mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-07 13:44:23 +08:00
Add form response models and controllers [SCI-11356]
This commit is contained in:
parent
6339c71d8f
commit
52e2c50b37
27 changed files with 787 additions and 7 deletions
39
app/controllers/form_field_values_controller.rb
Normal file
39
app/controllers/form_field_values_controller.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormFieldValuesController < ApplicationController
|
||||
before_action :load_form_response
|
||||
before_action :load_form_field
|
||||
before_action :check_create_permissions
|
||||
|
||||
def create
|
||||
@form_field_value = @form_response.create_value!(
|
||||
current_user,
|
||||
@form_field,
|
||||
form_field_value_params[:value]
|
||||
)
|
||||
|
||||
render json: @form_field_value, serializer: FormFieldValueSerializer, user: current_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def form_field_value_params
|
||||
params.require(:form_field_value).permit(:form_field_id, :value)
|
||||
end
|
||||
|
||||
def load_form_response
|
||||
@form_response = FormResponse.find_by(id: params[:form_response_id])
|
||||
|
||||
render_404 unless @form_response
|
||||
end
|
||||
|
||||
def load_form_field
|
||||
@form_field = @form_response.form.form_fields.find_by(id: form_field_value_params[:form_field_id])
|
||||
|
||||
render_404 unless @form_field
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
render_403 unless can_submit_form_response?(@form_response)
|
||||
end
|
||||
end
|
68
app/controllers/form_responses_controller.rb
Normal file
68
app/controllers/form_responses_controller.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormResponsesController < ApplicationController
|
||||
before_action :load_form, only: :create
|
||||
before_action :load_parent, only: :create
|
||||
before_action :load_form_response, except: :create
|
||||
|
||||
def create
|
||||
case @parent
|
||||
when Step
|
||||
render_403 and return unless can_create_protocol_form_responses?(@parent.protocol)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
@form_response = FormResponse.create!(form: @form, created_by: current_user)
|
||||
@parent.step_orderable_elements.create!(orderable: @form_response)
|
||||
end
|
||||
else
|
||||
render_422
|
||||
end
|
||||
|
||||
render json: @form_response, serializer: FormResponseSerializer, user: current_user
|
||||
end
|
||||
|
||||
def submit
|
||||
render_403 and return unless can_submit_form_response?(@form_response)
|
||||
|
||||
@form_response.submit!(current_user)
|
||||
|
||||
render json: @form_response, serializer: FormResponseSerializer, user: current_user
|
||||
end
|
||||
|
||||
def reset
|
||||
render_403 and return unless can_reset_form_response?(@form_response)
|
||||
|
||||
@form_response.reset!(current_user)
|
||||
|
||||
render json: @form_response, serializer: FormResponseSerializer, user: current_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def form_response_params
|
||||
params.require(:form_response).permit(:form_id, :parent_id, :parent_type)
|
||||
end
|
||||
|
||||
def load_form
|
||||
@form = Form.find_by(id: form_response_params[:form_id])
|
||||
|
||||
render_404 unless @form && can_read_form?(@form)
|
||||
end
|
||||
|
||||
def load_parent
|
||||
case form_response_params[:parent_type]
|
||||
when 'Step'
|
||||
@parent = Step.find_by(id: form_response_params[:parent_id])
|
||||
else
|
||||
return render_422
|
||||
end
|
||||
|
||||
render_404 unless @parent
|
||||
end
|
||||
|
||||
def load_form_response
|
||||
@form_response = FormResponse.find_by(id: params[:id])
|
||||
|
||||
render_404 unless @form_response
|
||||
end
|
||||
end
|
|
@ -17,6 +17,7 @@ class Form < ApplicationRecord
|
|||
belongs_to :default_public_user_role, class_name: 'UserRole', optional: true
|
||||
|
||||
has_many :form_fields, inverse_of: :form, dependent: :destroy
|
||||
has_many :form_responses, dependent: :destroy
|
||||
has_many :users, through: :user_assignments
|
||||
|
||||
validates :name, length: { minimum: Constants::NAME_MIN_LENGTH, maximum: Constants::NAME_MAX_LENGTH }
|
||||
|
@ -27,7 +28,7 @@ class Form < ApplicationRecord
|
|||
after_update :update_automatic_user_assignments,
|
||||
if: -> { saved_change_to_default_public_user_role_id? }
|
||||
|
||||
enum visibility: { hidden: 0, visible: 1 }
|
||||
enum :visibility, { hidden: 0, visible: 1 }
|
||||
|
||||
def permission_parent
|
||||
nil
|
||||
|
|
20
app/models/form_datetime_field_value.rb
Normal file
20
app/models/form_datetime_field_value.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormDatetimeFieldValue < FormFieldValue
|
||||
def value=(val)
|
||||
if val.is_a?(Array)
|
||||
self.datetime = val[0]
|
||||
self.datetime_to = val[1]
|
||||
else
|
||||
self.datetime = val
|
||||
end
|
||||
end
|
||||
|
||||
def value
|
||||
range? ? [datetime, datetime_to] : datetime
|
||||
end
|
||||
|
||||
def range?
|
||||
datetime_to.present?
|
||||
end
|
||||
end
|
|
@ -1,15 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormField < ApplicationRecord
|
||||
|
||||
belongs_to :form
|
||||
belongs_to :created_by, class_name: 'User'
|
||||
belongs_to :last_modified_by, class_name: 'User'
|
||||
has_many :form_field_values, dependent: :destroy
|
||||
|
||||
validates :name, length: { minimum: Constants::NAME_MIN_LENGTH, maximum: Constants::NAME_MAX_LENGTH }
|
||||
validates :description, length: { maximum: Constants::NAME_MAX_LENGTH }
|
||||
validates :position, presence: true, uniqueness: { scope: :form }
|
||||
|
||||
acts_as_list scope: :form, top_of_list: 0, sequential_updates: true
|
||||
|
||||
end
|
||||
|
|
18
app/models/form_field_value.rb
Normal file
18
app/models/form_field_value.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormFieldValue < ApplicationRecord
|
||||
belongs_to :form_response
|
||||
belongs_to :form_field
|
||||
belongs_to :created_by, class_name: 'User'
|
||||
belongs_to :submitted_by, class_name: 'User'
|
||||
|
||||
scope :latest, -> { where(latest: true) }
|
||||
|
||||
def value=(_)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def value
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
11
app/models/form_multiple_choice_field_value.rb
Normal file
11
app/models/form_multiple_choice_field_value.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormMultipleChoiceFieldValue < FormFieldValue
|
||||
def value=(val)
|
||||
self.selection = val
|
||||
end
|
||||
|
||||
def value
|
||||
selection
|
||||
end
|
||||
end
|
22
app/models/form_number_field_value.rb
Normal file
22
app/models/form_number_field_value.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormNumberFieldValue < FormFieldValue
|
||||
def value=(val)
|
||||
self.unit = form_field.data['unit']
|
||||
|
||||
if val.is_a?(Array)
|
||||
self.number = val[0]
|
||||
self.number_to = val[1]
|
||||
else
|
||||
self.number = val
|
||||
end
|
||||
end
|
||||
|
||||
def value
|
||||
range? ? [number, number_to] : number
|
||||
end
|
||||
|
||||
def range?
|
||||
number_to.present?
|
||||
end
|
||||
end
|
72
app/models/form_response.rb
Normal file
72
app/models/form_response.rb
Normal file
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class InvalidStatusError < StandardError; end
|
||||
|
||||
class FormResponse < ApplicationRecord
|
||||
include Discard::Model
|
||||
|
||||
default_scope -> { kept }
|
||||
|
||||
belongs_to :form
|
||||
belongs_to :created_by, class_name: 'User'
|
||||
belongs_to :submitted_by, class_name: 'User', optional: true
|
||||
|
||||
has_one :step_orderable_element, as: :orderable, dependent: :destroy
|
||||
|
||||
enum :status, { pending: 0, submitted: 1, locked: 2 }
|
||||
|
||||
has_many :form_field_values, dependent: :destroy
|
||||
|
||||
def step
|
||||
step_orderable_element&.step
|
||||
end
|
||||
|
||||
def parent
|
||||
step_orderable_element&.step
|
||||
end
|
||||
|
||||
def create_value!(created_by, form_field, value)
|
||||
ActiveRecord::Base.transaction(requires_new: true) do
|
||||
form_field_values.where(form_field: form_field).find_each do |form_field_value|
|
||||
form_field_value.update!(latest: false)
|
||||
end
|
||||
|
||||
"Form#{form_field.data['type']}Value".constantize.create!(
|
||||
form_field: form_field,
|
||||
form_response: self,
|
||||
# these can change if the form_response is reset, as submitted_by will be kept the same, but created_by will change
|
||||
created_by: created_by,
|
||||
submitted_by: created_by,
|
||||
value: value
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def submit!(user)
|
||||
update!(
|
||||
status: :submitted,
|
||||
submitted_by: user,
|
||||
submitted_at: DateTime.current
|
||||
)
|
||||
end
|
||||
|
||||
def reset!(user)
|
||||
raise InvalidStatusError, 'Cannot reset form that has not been submitted yet!' if status != 'submitted'
|
||||
|
||||
ActiveRecord::Base.transaction(requires_new: true) do
|
||||
new_form_response = dup
|
||||
new_form_response.update!(status: 'pending', created_by: user)
|
||||
|
||||
form_field_values.latest.find_each do |form_field_value|
|
||||
form_field_value.dup.update!(form_response: new_form_response, created_by: user)
|
||||
end
|
||||
|
||||
# if attached to step, reattach new form response
|
||||
self&.step_orderable_element&.update!(orderable: new_form_response)
|
||||
|
||||
discard
|
||||
|
||||
new_form_response
|
||||
end
|
||||
end
|
||||
end
|
11
app/models/form_single_choice_field_value.rb
Normal file
11
app/models/form_single_choice_field_value.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormSingleChoiceFieldValue < FormFieldValue
|
||||
def value=(val)
|
||||
self.text = val
|
||||
end
|
||||
|
||||
def value
|
||||
text
|
||||
end
|
||||
end
|
11
app/models/form_text_field_value.rb
Normal file
11
app/models/form_text_field_value.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormTextFieldValue < FormFieldValue
|
||||
def value=(val)
|
||||
self.text = val
|
||||
end
|
||||
|
||||
def value
|
||||
text
|
||||
end
|
||||
end
|
|
@ -61,6 +61,10 @@ class UserRole < ApplicationRecord
|
|||
predefined.find_by(name: UserRole.public_send('viewer_role').name)
|
||||
end
|
||||
|
||||
def self.find_predefined_technician_role
|
||||
predefined.find_by(name: UserRole.public_send('technician_role').name)
|
||||
end
|
||||
|
||||
def has_permission?(permission)
|
||||
permissions.include?(permission)
|
||||
end
|
||||
|
|
38
app/permissions/form_response.rb
Normal file
38
app/permissions/form_response.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Canaid::Permissions.register_for(FormResponse) do
|
||||
%i(
|
||||
submit_form_response
|
||||
reset_form_response
|
||||
).each do |perm|
|
||||
can perm do |_, form_response|
|
||||
!form_response.locked?
|
||||
end
|
||||
end
|
||||
|
||||
can :submit_form_response do |user, form_response|
|
||||
parent = form_response.parent
|
||||
case parent
|
||||
when Step
|
||||
next false unless parent.protocol.my_module # protocol template forms can't be submitted
|
||||
|
||||
parent.protocol.my_module.permission_granted?(user, FormResponsePermissions::SUBMIT)
|
||||
end
|
||||
end
|
||||
|
||||
can :reset_form_response do |user, form_response|
|
||||
parent = form_response.parent
|
||||
case parent
|
||||
when Step
|
||||
next false unless parent.protocol.my_module # protocol template forms can't be reset
|
||||
|
||||
parent.protocol.my_module.permission_granted?(user, FormResponsePermissions::SUBMIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Canaid::Permissions.register_for(Protocol) do
|
||||
can :create_protocol_form_responses do |user, protocol|
|
||||
(protocol.my_module || protocol).permission_granted?(user, FormResponsePermissions::CREATE)
|
||||
end
|
||||
end
|
11
app/serializers/form_field_value_serializer.rb
Normal file
11
app/serializers/form_field_value_serializer.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormFieldValueSerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
|
||||
attributes :form_field_id, :type, :value, :submitted_at, :submitted_by_full_name, :unit
|
||||
|
||||
def submitted_by_full_name
|
||||
object.submitted_by.full_name
|
||||
end
|
||||
end
|
15
app/serializers/form_response_serializer.rb
Normal file
15
app/serializers/form_response_serializer.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormResponseSerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
|
||||
attributes :id, :created_at, :form_id
|
||||
|
||||
has_many :form_field_values do
|
||||
object.form_field_values.latest
|
||||
end
|
||||
|
||||
def submitted_by_full_name
|
||||
object.submitted_by.full_name
|
||||
end
|
||||
end
|
|
@ -139,7 +139,15 @@ class Extends
|
|||
RepositoryStockValue
|
||||
)
|
||||
|
||||
STI_PRELOAD_CLASSES = %w(LinkedRepository SoftLockedRepository)
|
||||
STI_PRELOAD_CLASSES = %w(
|
||||
LinkedRepository
|
||||
SoftLockedRepository
|
||||
FormTextFieldValue
|
||||
FormNumberFieldValue
|
||||
FormDatetimeFieldValue
|
||||
FormMultipleChoiceFieldValue
|
||||
FormSingleChoiceFieldValue
|
||||
)
|
||||
|
||||
# Array of preload relations used in search query for repository rows
|
||||
REPOSITORY_ROWS_PRELOAD_RELATIONS = []
|
||||
|
|
|
@ -44,6 +44,15 @@ module PermissionExtends
|
|||
).each { |permission| const_set(permission, "form_#{permission.parameterize}") }
|
||||
end
|
||||
|
||||
module FormResponsePermissions
|
||||
%w(
|
||||
NONE
|
||||
CREATE
|
||||
SUBMIT
|
||||
RESET
|
||||
).each { |permission| const_set(permission, "form_response_#{permission.parameterize}") }
|
||||
end
|
||||
|
||||
module ReportPermissions
|
||||
%w(
|
||||
NONE
|
||||
|
@ -156,8 +165,9 @@ module PermissionExtends
|
|||
ProjectPermissions.constants.map { |const| ProjectPermissions.const_get(const) } +
|
||||
ExperimentPermissions.constants.map { |const| ExperimentPermissions.const_get(const) } +
|
||||
MyModulePermissions.constants.map { |const| MyModulePermissions.const_get(const) } +
|
||||
RepositoryPermissions.constants.map { |const| RepositoryPermissions.const_get(const) }
|
||||
).reject { |p| p.end_with?("_none") }
|
||||
RepositoryPermissions.constants.map { |const| RepositoryPermissions.const_get(const) } +
|
||||
FormResponsePermissions.constants.map { |const| FormResponsePermissions.const_get(const) }
|
||||
).reject { |p| p.end_with?('_none') }
|
||||
|
||||
NORMAL_USER_PERMISSIONS = [
|
||||
TeamPermissions::PROJECTS_CREATE,
|
||||
|
@ -177,6 +187,9 @@ module PermissionExtends
|
|||
ProtocolPermissions::MANAGE_DRAFT,
|
||||
FormPermissions::READ,
|
||||
FormPermissions::READ_ARCHIVED,
|
||||
FormResponsePermissions::CREATE,
|
||||
FormResponsePermissions::SUBMIT,
|
||||
FormResponsePermissions::RESET,
|
||||
ReportPermissions::READ,
|
||||
ReportPermissions::MANAGE,
|
||||
ProjectPermissions::READ,
|
||||
|
@ -265,7 +278,8 @@ module PermissionExtends
|
|||
MyModulePermissions::REPOSITORY_ROWS_ASSIGN,
|
||||
MyModulePermissions::REPOSITORY_ROWS_MANAGE,
|
||||
MyModulePermissions::USERS_READ,
|
||||
MyModulePermissions::STOCK_CONSUMPTION_UPDATE
|
||||
MyModulePermissions::STOCK_CONSUMPTION_UPDATE,
|
||||
FormResponsePermissions::SUBMIT
|
||||
]
|
||||
|
||||
VIEWER_PERMISSIONS = [
|
||||
|
|
|
@ -874,6 +874,15 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :form_responses, only: %i(create) do
|
||||
member do
|
||||
post :submit
|
||||
post :reset
|
||||
end
|
||||
|
||||
resources :form_field_values, only: %i(create)
|
||||
end
|
||||
|
||||
get 'search' => 'search#index'
|
||||
get 'search/new' => 'search#new', as: :new_search
|
||||
resource :search, only: [], controller: :search do
|
||||
|
|
16
db/migrate/20241213095430_create_form_responses.rb
Normal file
16
db/migrate/20241213095430_create_form_responses.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateFormResponses < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :form_responses do |t|
|
||||
t.references :form, null: false, foreign_key: true
|
||||
t.references :created_by, null: false, foreign_key: { to_table: :users }
|
||||
t.references :submitted_by, null: true, foreign_key: { to_table: :users }
|
||||
t.integer :status, default: 0
|
||||
t.datetime :submitted_at
|
||||
t.datetime :discarded_at
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
33
db/migrate/20241213100803_create_form_field_values.rb
Normal file
33
db/migrate/20241213100803_create_form_field_values.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateFormFieldValues < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :form_field_values do |t|
|
||||
t.string :type, index: true
|
||||
t.references :form_response, null: false, foreign_key: true
|
||||
t.references :form_field, null: false, foreign_key: true
|
||||
t.references :created_by, null: false, foreign_key: { to_table: :users }
|
||||
t.references :submitted_by, null: true, foreign_key: { to_table: :users }
|
||||
t.timestamp :submitted_at
|
||||
t.boolean :latest, null: false, default: true
|
||||
t.boolean :not_applicable, null: false, default: false
|
||||
|
||||
# FormFieldDateTimeValue
|
||||
t.datetime :datetime
|
||||
t.datetime :datetime_to
|
||||
|
||||
# FormFieldNumberValue
|
||||
t.decimal :number
|
||||
t.decimal :number_to
|
||||
t.text :unit
|
||||
|
||||
# FormFieldTextValue, FormFieldSingleChoiceValue
|
||||
t.text :text
|
||||
|
||||
# FormFieldMultipleCohiceValue
|
||||
t.text :selection, array: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
40
db/migrate/20241218085759_add_form_response_permissions.rb
Normal file
40
db/migrate/20241218085759_add_form_response_permissions.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddFormResponsePermissions < ActiveRecord::Migration[7.0]
|
||||
FORM_RESPONSE_MANAGE_PERMISSION = [
|
||||
FormResponsePermissions::RESET,
|
||||
FormResponsePermissions::CREATE
|
||||
].freeze
|
||||
|
||||
FORM_RESPONSE_SUBMIT_PERMISSION = [
|
||||
FormResponsePermissions::SUBMIT
|
||||
].freeze
|
||||
|
||||
def up
|
||||
@owner_role = UserRole.find_predefined_owner_role
|
||||
@normal_user_role = UserRole.find_predefined_normal_user_role
|
||||
@technician_user_role = UserRole.find_predefined_technician_role
|
||||
|
||||
@owner_role.permissions = @owner_role.permissions | (FORM_RESPONSE_SUBMIT_PERMISSION + FORM_RESPONSE_MANAGE_PERMISSION)
|
||||
@normal_user_role.permissions = @normal_user_role.permissions | (FORM_RESPONSE_SUBMIT_PERMISSION + FORM_RESPONSE_MANAGE_PERMISSION)
|
||||
@technician_user_role.permissions = @technician_user_role.permissions | FORM_RESPONSE_SUBMIT_PERMISSION
|
||||
|
||||
@owner_role.save(validate: false)
|
||||
@normal_user_role.save(validate: false)
|
||||
@technician_user_role.save(validate: false)
|
||||
end
|
||||
|
||||
def down
|
||||
@owner_role = UserRole.find_predefined_owner_role
|
||||
@normal_user_role = UserRole.find_predefined_normal_user_role
|
||||
@technician_user_role = UserRole.find_predefined_technician_role
|
||||
|
||||
@owner_role.permissions = @owner_role.permissions - (FORM_RESPONSE_SUBMIT_PERMISSION + FORM_RESPONSE_MANAGE_PERMISSION)
|
||||
@normal_user_role.permissions = @normal_user_role.permissions - (FORM_RESPONSE_SUBMIT_PERMISSION + FORM_RESPONSE_MANAGE_PERMISSION)
|
||||
@technician_user_role.permissions = @technician_user_role.permissions - FORM_RESPONSE_SUBMIT_PERMISSION
|
||||
|
||||
@owner_role.save(validate: false)
|
||||
@normal_user_role.save(validate: false)
|
||||
@technician_user_role.save(validate: false)
|
||||
end
|
||||
end
|
94
spec/controllers/form_field_values_controller_spec.rb
Normal file
94
spec/controllers/form_field_values_controller_spec.rb
Normal file
|
@ -0,0 +1,94 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe FormFieldValuesController, type: :controller do
|
||||
login_user
|
||||
|
||||
include_context 'reference_project_structure'
|
||||
|
||||
let!(:form) { create(:form, team: team, created_by: user) }
|
||||
let!(:form_response) { create(:form_response, form: form, created_by: user) }
|
||||
let!(:form_field) { create(:form_field, form: form, created_by: user, data: { type: 'TextField' }) }
|
||||
|
||||
describe 'POST create' do
|
||||
let(:action) { post :create, params: params, format: :json }
|
||||
let(:params) do
|
||||
{
|
||||
form_response_id: form_response.id,
|
||||
form_field_value: {
|
||||
form_field_id: form_field.id,
|
||||
value: 'Test value'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when user has permissions' do
|
||||
before { allow(controller).to receive(:can_submit_form_response?).and_return(true) }
|
||||
|
||||
it 'creates a form field value successfully' do
|
||||
expect { action }.to change(FormFieldValue, :count).by(1)
|
||||
expect(response).to have_http_status(:success)
|
||||
response_body = JSON.parse(response.body)
|
||||
expect(response_body['data']['attributes']['value']).to eq 'Test value'
|
||||
expect(response_body['data']['attributes']['form_field_id']).to eq form_field.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user lacks permissions' do
|
||||
before { allow(controller).to receive(:can_submit_form_response?).and_return(false) }
|
||||
|
||||
it 'returns a forbidden response' do
|
||||
action
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid form_field_id is provided' do
|
||||
let(:params) do
|
||||
{
|
||||
form_response_id: form_response.id,
|
||||
form_field_value: {
|
||||
form_field_id: -1,
|
||||
value: 'Test value'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns a not found response' do
|
||||
action
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid form_response_id is provided' do
|
||||
let(:params) do
|
||||
{
|
||||
form_response_id: 0,
|
||||
form_field_value: {
|
||||
form_field_id: form_field.id,
|
||||
value: 'Test value'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns a not found response' do
|
||||
action
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when missing value parameter' do
|
||||
let(:params) do
|
||||
{
|
||||
form_response_id: form_response.id,
|
||||
nothing: {}
|
||||
}
|
||||
end
|
||||
|
||||
it 'raises a parameter missing error' do
|
||||
expect { action }.to raise_error(ActionController::ParameterMissing)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
128
spec/controllers/form_responses_controller_spec.rb
Normal file
128
spec/controllers/form_responses_controller_spec.rb
Normal file
|
@ -0,0 +1,128 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe FormResponsesController, type: :controller do
|
||||
login_user
|
||||
|
||||
include_context 'reference_project_structure'
|
||||
|
||||
let!(:form) { create(:form, team: team, created_by: user) }
|
||||
let!(:step) { create(:step, protocol: protocol) }
|
||||
let!(:protocol) { create(:protocol, added_by: user) }
|
||||
let!(:form_response) { create(:form_response, form: form, created_by: user) }
|
||||
|
||||
describe 'POST create' do
|
||||
let(:action) { post :create, params: params, format: :json }
|
||||
let(:params) do
|
||||
{
|
||||
form_response: {
|
||||
form_id: form.id,
|
||||
parent_id: step.id,
|
||||
parent_type: 'Step'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when user has permissions' do
|
||||
before { allow(controller).to receive(:can_create_protocol_form_responses?).and_return(true) }
|
||||
|
||||
it 'creates a form response successfully' do
|
||||
expect { action }.to change(FormResponse, :count).by(1)
|
||||
expect(response).to have_http_status(:success)
|
||||
response_body = JSON.parse(response.body)
|
||||
expect(response_body['data']['attributes']['form_id']).to eq form.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user lacks permissions' do
|
||||
before { allow(controller).to receive(:can_create_protocol_form_responses?).and_return(false) }
|
||||
|
||||
it 'returns a forbidden response' do
|
||||
action
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid parent type' do
|
||||
let(:params) do
|
||||
{
|
||||
form_response: {
|
||||
form_id: form.id,
|
||||
parent_id: step.id,
|
||||
parent_type: 'InvalidType'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns an unprocessable entity response' do
|
||||
action
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT submit' do
|
||||
let(:action) { put :submit, params: { id: form_response.id }, format: :json }
|
||||
|
||||
context 'when user has permissions' do
|
||||
before { allow(controller).to receive(:can_submit_form_response?).and_return(true) }
|
||||
|
||||
it 'submits the form response successfully' do
|
||||
expect(form_response.status).to eq 'pending'
|
||||
action
|
||||
expect(response).to have_http_status(:success)
|
||||
form_response.reload
|
||||
expect(form_response.status).to eq 'submitted'
|
||||
expect(form_response.submitted_by).to eq user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user lacks permissions' do
|
||||
before { allow(controller).to receive(:can_submit_form_response?).and_return(false) }
|
||||
|
||||
it 'returns a forbidden response' do
|
||||
action
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT reset' do
|
||||
let(:action) { put :reset, params: { id: form_response.id }, format: :json }
|
||||
|
||||
context 'when user has permissions and form is submitted' do
|
||||
before do
|
||||
allow(controller).to receive(:can_reset_form_response?).and_return(true)
|
||||
form_response.update!(status: 'submitted')
|
||||
end
|
||||
|
||||
it 'resets the form response successfully' do
|
||||
expect { action }.to change(FormResponse.unscoped, :count).by(1)
|
||||
expect(response).to have_http_status(:success)
|
||||
form_response.reload
|
||||
expect(form_response.discarded?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when form is not submitted' do
|
||||
before do
|
||||
allow(controller).to receive(:can_reset_form_response?).and_return(true)
|
||||
form_response.update!(status: 'pending')
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { action }.to raise_error(InvalidStatusError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user lacks permissions' do
|
||||
before { allow(controller).to receive(:can_reset_form_response?).and_return(false) }
|
||||
|
||||
it 'returns a forbidden response' do
|
||||
action
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
spec/factories/form_field_values.rb
Normal file
12
spec/factories/form_field_values.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :form_field_value do
|
||||
type { 'FormFieldTextValue' }
|
||||
text { 'hello' }
|
||||
association :form_response
|
||||
association :form_field
|
||||
association :created_by, factory: :user
|
||||
association :submitted_by, factory: :user
|
||||
end
|
||||
end
|
15
spec/factories/form_responses.rb
Normal file
15
spec/factories/form_responses.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :form_response do
|
||||
association :form
|
||||
association :created_by, factory: :user
|
||||
status { :pending }
|
||||
|
||||
trait :submitted do
|
||||
status { :submitted }
|
||||
association :submitted_by, factory: :user
|
||||
submitted_at { DateTime.current }
|
||||
end
|
||||
end
|
||||
end
|
39
spec/models/form_field_value_spec.rb
Normal file
39
spec/models/form_field_value_spec.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe FormFieldValue, type: :model do
|
||||
let(:form_response) { build :form_field_value }
|
||||
|
||||
it 'is valid' do
|
||||
expect(form_response).to be_valid
|
||||
end
|
||||
|
||||
it 'should be of class form field value' do
|
||||
expect(subject.class).to eq FormFieldValue
|
||||
end
|
||||
|
||||
describe 'Database table' do
|
||||
it { should have_db_column :form_response_id }
|
||||
it { should have_db_column :created_by_id }
|
||||
it { should have_db_column :submitted_by_id }
|
||||
it { should have_db_column :submitted_at }
|
||||
it { should have_db_column :created_at }
|
||||
it { should have_db_column :updated_at }
|
||||
it { should have_db_column :latest }
|
||||
it { should have_db_column :not_applicable }
|
||||
it { should have_db_column :datetime }
|
||||
it { should have_db_column :datetime_to }
|
||||
it { should have_db_column :number }
|
||||
it { should have_db_column :number_to }
|
||||
it { should have_db_column :text }
|
||||
it { should have_db_column :selection }
|
||||
end
|
||||
|
||||
describe 'Relations' do
|
||||
it { should belong_to(:form_response) }
|
||||
it { should belong_to(:form_field) }
|
||||
it { should belong_to(:created_by).class_name('User') }
|
||||
it { should belong_to(:submitted_by).class_name('User') }
|
||||
end
|
||||
end
|
32
spec/models/form_response_spec.rb
Normal file
32
spec/models/form_response_spec.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe FormResponse, type: :model do
|
||||
let(:form_response) { build :form_response }
|
||||
|
||||
it 'is valid' do
|
||||
expect(form_response).to be_valid
|
||||
end
|
||||
|
||||
it 'should be of class form field' do
|
||||
expect(subject.class).to eq FormResponse
|
||||
end
|
||||
|
||||
describe 'Database table' do
|
||||
it { should have_db_column :form_id }
|
||||
it { should have_db_column :created_by_id }
|
||||
it { should have_db_column :submitted_by_id }
|
||||
it { should have_db_column :status }
|
||||
it { should have_db_column :submitted_at }
|
||||
it { should have_db_column :discarded_at }
|
||||
it { should have_db_column :created_at }
|
||||
it { should have_db_column :updated_at }
|
||||
end
|
||||
|
||||
describe 'Relations' do
|
||||
it { should belong_to(:form) }
|
||||
it { should belong_to(:created_by).class_name('User') }
|
||||
it { should belong_to(:submitted_by).class_name('User').optional }
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue