diff --git a/Gemfile b/Gemfile index dcf00dc..1142c27 100644 --- a/Gemfile +++ b/Gemfile @@ -185,6 +185,9 @@ gem 'activerecord-session_store' +# JSON Schema validation +gem 'json_schemer' + # ------------------------------------------------ # ENVIRONMENT SPECIFIC DEPENDENCIES diff --git a/Gemfile.lock b/Gemfile.lock index 0a46c6c..f63dc22 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -118,6 +118,8 @@ dragonfly-s3_data_store (1.3.0) dragonfly (~> 1.0) fog-aws + ecma-re-validator (0.2.0) + regexp_parser (~> 1.2) erubi (1.9.0) erubis (2.7.0) eventmachine (1.2.7) @@ -186,6 +188,7 @@ guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) + hana (1.3.5) hashdiff (1.0.0) hashie (3.6.0) highline (2.0.2) @@ -201,6 +204,11 @@ activesupport (>= 3.0.0) multi_json (>= 1.2) json (2.2.0) + json_schemer (0.2.10) + ecma-re-validator (~> 0.2) + hana (~> 1.3) + regexp_parser (~> 1.5) + uri_template (~> 0.7) jwt (2.2.1) kaminari (1.1.1) activesupport (>= 4.1.0) @@ -443,6 +451,7 @@ thread_safe (~> 0.1) unicode-display_width (1.6.0) uniform_notifier (1.12.1) + uri_template (0.7.0) warden (1.2.7) rack (>= 1.0) web-console (3.3.0) @@ -509,6 +518,7 @@ guard-rspec htmltoword (= 1.1.0) jbuilder (~> 2.6.0) + json_schemer kaminari ledermann-rails-settings mini_racer diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index d52b8c1..12394a8 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# rubocop:disable class AnswersController < ApplicationController prepend Dmpopidor::Controllers::Answers @@ -136,7 +137,7 @@ def permitted_params permitted = params.require(:answer).permit(:id, :text, :plan_id, :user_id, :question_id, :lock_version, - :research_output_id, :is_common, + :research_output_id, :is_common, question_option_ids: []) # If question_option_ids has been filtered out because it was a # scalar value (e.g. radiobutton answer) diff --git a/app/views/branded/questions/_new_edit_question_structured.erb b/app/views/branded/questions/_new_edit_question_structured.erb index 48986b3..687d535 100644 --- a/app/views/branded/questions/_new_edit_question_structured.erb +++ b/app/views/branded/questions/_new_edit_question_structured.erb @@ -3,13 +3,14 @@

<%= question.text %>

<% schema['properties'].each do |key, prop| %> + <% value = answer.structured_answer.data[key] unless answer.structured_answer.nil? %> <% case prop['type'] %> <% when 'string' %> - <%= create_text_field(f, nil, key, prop['label']) %> + <%= create_text_field(f, value, key, prop['label']) %> <% when 'integer' %> - <%= create_number_field(f, nil, key, prop['label']) %> + <%= create_number_field(f, value, key, prop['label']) %> <% when 'boolean' %> - <%= create_checkbox_field(f, nil, key, prop['label']) %> + <%= create_checkbox_field(f, value, key, prop['label']) %> <% end %> <% end %> -
\ No newline at end of file + diff --git a/app/views/branded/questions/fields/_text_field.html.erb b/app/views/branded/questions/fields/_text_field.html.erb index 8938d15..1a88f05 100644 --- a/app/views/branded/questions/fields/_text_field.html.erb +++ b/app/views/branded/questions/fields/_text_field.html.erb @@ -9,4 +9,4 @@ <% end %> <% end %> - \ No newline at end of file + diff --git a/lib/dmpopidor/controllers/answers.rb b/lib/dmpopidor/controllers/answers.rb index 96faa5c..d8ad265 100644 --- a/lib/dmpopidor/controllers/answers.rb +++ b/lib/dmpopidor/controllers/answers.rb @@ -34,17 +34,27 @@ # 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] + @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 - @answer.update(p_params.merge(user_id: current_user.id)) + p = p_params.merge(user_id: current_user.id).select { |k, v| !schema_params.include?(k) } + @answer.update(p) 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 + form_data = p_params.select { |k, v| schema_params.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 if q.question_format.rda_metadata? @answer.update_answer_hash( JSON.parse(params[:standards]), p_params[:text] @@ -52,9 +62,19 @@ @answer.save! end rescue ActiveRecord::RecordNotFound - @answer = Answer.new(p_params.merge(user_id: current_user.id)) + p = p_params.merge(user_id: current_user.id).select { |k, v| !schema_params.include?(k) } + @answer = Answer.new(p) @answer.lock_version = 1 authorize @answer + if q.question_format.structured + form_data = p_params.select { |k, v| schema_params.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 if q.question_format.rda_metadata? @answer.update_answer_hash( JSON.parse(params[:standards]), p_params[:text] @@ -131,6 +151,69 @@ # rubocop:enable LineLength end end + + private + + 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 + + def permitted_params_from_properties(properties) + parameters = Array.new + properties.each do |key, prop| + if prop["type"] == "array" + parameters.append({key => []}) + else + parameters.append(key) + end + end + parameters + end + + def json_schema + question = Question.find(params['question_id']) + + question.structured_data_schema.schema + end + + def schema_params + permitted_params_from_properties(json_schema['properties']) + end + + def permitted_params + permitted = params.require(:answer).permit([:id, :text, :plan_id, :user_id, + :question_id, :lock_version, + :research_output_id, :is_common, + question_option_ids: []].append(schema_params)) + # 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 end end - end \ No newline at end of file + end diff --git a/test.json b/test.json index 1c5d1b4..b440079 100644 --- a/test.json +++ b/test.json @@ -25,4 +25,4 @@ "label": "Active?" } } -} \ No newline at end of file +} diff --git a/test.rb b/test.rb index 4de7ed2..f7f9847 100644 --- a/test.rb +++ b/test.rb @@ -5,4 +5,4 @@ data = JSON.load(file) sds = StructuredDataSchema.create(label: 'test', name: 'test', version: 1, schema: data.to_json, org_id: org.id, object: 'foo') qf = QuestionFormat.create(title: "Structured", description: "foo", structured: true) -question.update(question_format: qf, structured_data_schema: sds) \ No newline at end of file +question.update(question_format: qf, structured_data_schema: sds) diff --git a/test2.json b/test2.json new file mode 100644 index 0000000..ede82b9 --- /dev/null +++ b/test2.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A person", + "type":"object", + "properties": { + "lastName":{ + "description": "The last name of the person", + "type": "string", + "label": "Nom" + }, + "firstName": { + "description": "This person's first name", + "type": "string", + "label": "Prénom" + }, + "mbox": { + "description": "This person's mail addres", + "type": "string", + "format": "email", + "label": "Courriel" + }, + "personId": { + "description": "This person's identifier, e.g. ORCID Id", + "type": "string", + "label": "Identifiant" + } + }, + + "required": ["lastName","firstName","mbox"] +} diff --git a/test3.json b/test3.json new file mode 100644 index 0000000..33b7b2a --- /dev/null +++ b/test3.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "The description of license data", + "type":"object", + "properties": { + "title":{ + "description": "The title of this license. The value is a quoted string followed by '@' and a 2 letter language code", + "type": "string", + "pattern": "\".*\"@[a-z][a-z]" + }, + "licenseUrl": { + "description": "The URL where a description of this certification can be found", + "type": "string", + "format": "uri" + }, + "startDate": { + "description": "The date at which this data is subject to the license", + "type": "string", + "format": "date" + }, + "endDate": { + "description": "The date at which this data is no longer subject to the license", + "type": "string", + "format": "date" + } + }, + + "required": ["title","certificationUrl","startDate","endDate"] +}