typed_form 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8fdad9dc3081cffe611c965b36e76d7e2fe8f65f
4
- data.tar.gz: 42e9753fbd1c0e3e0c649f8e24666fdcfe6fe120
3
+ metadata.gz: d67800ceba46ef713f9fc5aa6d60390ad29d1737
4
+ data.tar.gz: dfe2b5bcf4c885ad9883af07d6a92f86d107572c
5
5
  SHA512:
6
- metadata.gz: 3c24d68be4bbf0608ea8b937748b3345ab93880577f0f08d5d5a2ad6a09da5e080c3a83e7336afff336925921c965cad79a55facf5b6dec23e0a2240c18c7263
7
- data.tar.gz: f8b605773edcbd28a69c3eff49f4a21d0c4bc23ee3d45c87c0a93cc017a53a7c8edccc4bb2de2ea67cc662e395df491aea09fce2723527570ca8e5bbfdbee254
6
+ metadata.gz: 3817ca0f77392d2846dca9b7f7b51fc00a5685d3caacf5a25dd90f6ec7a6a4aefa9cffa92a5d0eb8218b05099234ea14af6631512463b8edf9a8c7ee08d94b01
7
+ data.tar.gz: c1ed19b2752ec0e4d3fed9a2a20a913ba751c18bbf7af79ec79f359a070b4a8d6792f559f2fc65241a7cdbe3e96a7bc9a563877d1c9dad1f04fa9876d072b3af
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Change Log
2
2
 
3
+ ## [0.1.0] - 2017-03-12
4
+
5
+ Adds logic for extracting data needed for Data API from incoming Webhooks.
6
+
7
+ Adds YARD documentation to gem.
8
+
9
+ Separates API/Client, Data-API Form Data, and Form/Webhook responsibilities into isolated namespaces. Stabilizes Gem API around accessing form data via
10
+ API and loading from existing JSON data sources.
11
+
3
12
  ## [0.0.4] - 2017-03-10
4
13
 
5
14
  Defaults to freezing Questions after initialization, to prevent concerns with mutating data inadvertently when working with questions as value objects.
data/README.md CHANGED
@@ -78,13 +78,15 @@ form_id = "typeform_form_id"
78
78
  # individual response token, provided in responses hash or via webhook data
79
79
  token = "form_token"
80
80
 
81
- client = TypedForm::Client.new(api_key: api_key)
82
- json = client.find_form_by(form_id: form_id, token: token)
83
- => # json response for that specific form submission
84
-
85
- parsed = TypedForm::JSONResponseHandler.new(json)
86
- form = TypedForm::FormResponse.new(parsed_questions: parsed.questions,
87
- parsed_response: parsed.response.first)
81
+ client = TypedForm::API::Client.new(api_key: api_key)
82
+ form = TypedForm::Form.find_form_by(
83
+ client: client,
84
+ form_id: form_id,
85
+ token: token
86
+ )
87
+
88
+ # Or, load from existing JSON
89
+ form = TypedForm::Form.build_form_from(json: your_json_source)
88
90
  questions = form.questions
89
91
 
90
92
  questions.first.text
@@ -103,6 +105,8 @@ questions.first.type
103
105
 
104
106
  The most common use case for this is extrapolating question and answers into a simple object that can provide a clean interface for displaying them. Question type information can be used to allow helpers to format and display different field types (most specifically dates) in a more user-friendly format.
105
107
 
108
+ Additional documentation is available at [Rubydoc.](http://www.rubydoc.info/github/useed/typed_form)
109
+
106
110
  ## Development
107
111
 
108
112
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,53 @@
1
+ module TypedForm
2
+ # Objects related to working with the Typeform API.
3
+ module API
4
+ # API wrapper for querying typeform's Data API.
5
+ # @attr [String] api_key Your Typeform API key.
6
+ # @see https://www.typeform.com/help/data-api/ Typeform Data API docs
7
+ class Client
8
+ attr_reader :api_key
9
+
10
+ include HTTParty
11
+
12
+ # Creates a new instance of an API client for querying the
13
+ # Typeform Data API.
14
+ # @param [String] api_key Typeform API Key
15
+ def initialize(api_key:)
16
+ @api_key = api_key
17
+ end
18
+
19
+ # Queries the Typeform Data API /form/ endpoint to retrieve a specific
20
+ # response (filtered by token) for a specific Form.
21
+ #
22
+ # @param [String] form_id Typeform Form ID
23
+ # @param [String] token The token for the form response you are querying
24
+ # @param [Hash<Object>] query_params Splats and passes along query
25
+ # parameters to the form's query. See
26
+ # https://www.typeform.com/help/data-api/ under Filtering Options for
27
+ # more information.
28
+ def find_form_by(form_id:, token:, **query_params)
29
+ forms_by_id(form_id: form_id, token: token, **query_params)
30
+ end
31
+
32
+ private
33
+
34
+ def forms_by_id(form_id:, **query_params)
35
+ url_params = query_params.map { |k, v| "#{k}=#{v}" }
36
+ request_url = [form_id, authenticated_slug(url_params)].join("?")
37
+ get(request_url).body
38
+ end
39
+
40
+ def get(slug)
41
+ self.class.get(base_url + slug)
42
+ end
43
+
44
+ def base_url
45
+ "https://api.typeform.com/v1/form/"
46
+ end
47
+
48
+ def authenticated_slug(url_params)
49
+ ["key=#{api_key}", url_params].join("&")
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,62 @@
1
+ module TypedForm
2
+ # A representation of the Typeform Form Data for a single
3
+ # form response.
4
+ #
5
+ # @attr_reader [String] json JSON data using Typeform's Data API schema
6
+ class Form
7
+ extend Forwardable
8
+ attr_accessor :json
9
+
10
+ # @!method responses
11
+ # @see FormData::ParsedJson#responses
12
+ def_delegators :parsed_json, :responses
13
+
14
+ # @!method questions
15
+ # @see FormSubmission#questions
16
+ def_delegators :submission, :questions
17
+
18
+ # Creates a new instance of a Form, to allow querying
19
+ def initialize(json: json)
20
+ @json = json
21
+ end
22
+
23
+ # Uses the Typeform API client to query/find the form based on the form_id
24
+ # and token, then builds a new Form from that JSON request.
25
+ # @param [API::Client] client Typeform API client instance
26
+ # @param [String] form_id Form ID you're querying
27
+ # @param [String] token The token for the response you're retrieving
28
+ # @return [Form] A Form, via JSON fetched from Typeform's API
29
+ def self.find_form_by(client:, form_id:, token:)
30
+ json = client.find_form_by(form_id: form_id, token: token)
31
+ new(json: json)
32
+ end
33
+
34
+ # Builds a hash of Questions matched with Answers.
35
+ # @return [Hash] A Hash matching { "Question" => "Answer" } format.
36
+ def to_hash
37
+ questions.each_with_object({}) { |q, hash| hash[q.text] = q.answer }
38
+ end
39
+
40
+ private
41
+
42
+ def submission
43
+ raise StandardError, "Form expects a single response" if multi_response?
44
+ @_submission ||= FormData::FormSubmission.new(
45
+ parsed_questions: parsed_json.questions,
46
+ parsed_response: responses.first
47
+ )
48
+ end
49
+
50
+ def multi_response?
51
+ responses.size > 1
52
+ end
53
+
54
+ def parsed_json
55
+ @_parsed_json ||= FormData::ParsedJson.new(json: json)
56
+ end
57
+
58
+ def response
59
+ responses.first
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,91 @@
1
+ module TypedForm
2
+ # A collection of Modules that build value objects around data from the
3
+ # Typeform Data API.
4
+ module FormData
5
+ # A collection class which takes a collection of answers to a form, and
6
+ # associates the questions with answers.
7
+ #
8
+ # @attr_reader [Arendelle] parsed_response An immutable object representing
9
+ # the submission data from the form.
10
+ # @attr_reader [Arendelle] parsed_questions An immutable object representing
11
+ # the question data from the form.
12
+ class Answers
13
+ extend Forwardable
14
+
15
+ # @!method answers
16
+ # @return [Arendelle] Parsed Questions from Typeform Data API JSON
17
+ # @!method metadata
18
+ # @return [Arendelle] Parsed Metadata from Typeform Data API JSON
19
+ # @!method token
20
+ # @return [String] Token extracted from Typeform Data API JSON
21
+ def_delegators :parsed_response, :answers, :metadata, :token
22
+
23
+ # @!method token
24
+ # @return [String] date form was submitted, in UTC
25
+ def_delegators :metadata, :date_submit
26
+
27
+ attr_reader :parsed_response, :input_questions, :parsed_questions
28
+
29
+ # Builds a collection of Questions, with text extrapolated to support
30
+ # "piped" questions (i.e. "What is the name of your {{answer_42}}").
31
+ #
32
+ # @return [Array<Question>] Question value objects with extrapolated
33
+ # text and answers.
34
+ def self.collate(parsed_response:, parsed_questions:, input_questions:)
35
+ new(parsed_response: parsed_response,
36
+ parsed_questions: parsed_questions,
37
+ input_questions: input_questions).questions
38
+ end
39
+
40
+ # Iterates through the existing collection of input questions to build a
41
+ # set of question value objects. Memoizes result.
42
+ # @return [Array<Question>]
43
+ def questions
44
+ @_questions ||= input_questions.map do |question|
45
+ Question.with_response_data(
46
+ question: question,
47
+ answer: answers_for(question.ids),
48
+ text: extrapolated_question_text(question)
49
+ )
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ private_class_method :new
56
+ def initialize(parsed_response:, input_questions:, parsed_questions:)
57
+ @parsed_response = parsed_response
58
+ @input_questions = input_questions
59
+ @parsed_questions = parsed_questions
60
+ end
61
+
62
+ def answers_for(ids)
63
+ id_answers = ids.map { |id| find_answer_by_id(id) }.compact
64
+ return if id_answers.size.zero?
65
+ id_answers.join(", ")
66
+ end
67
+
68
+ def extrapolated_question_text(question)
69
+ regex = %r(\{\{answer_(\d+)\}\})
70
+ id_match = question.original_text.match(regex)
71
+ return question.original_text unless id_match
72
+
73
+ question.original_text.gsub(regex, find_answer_by_field_id(id_match[1]))
74
+ end
75
+
76
+ def find_answer_by_field_id(id)
77
+ fields = parsed_questions.select do |question|
78
+ question.field_id == id.to_i
79
+ end
80
+
81
+ answers_found = fields.map { |f| find_answer_by_id(f.id) }.compact
82
+ return find_answer_by_id(fields.first.id) if answers_found.size == 1
83
+ raise ArgumentError, "Cannot find single answer with field ID ##{id}"
84
+ end
85
+
86
+ def find_answer_by_id(id)
87
+ answers.instance_variable_get("@#{id}")
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,53 @@
1
+ module TypedForm
2
+ module FormData
3
+ # Takes an individual parsed response for a series of questions, and
4
+ # provides an interface for accessing the Question value objects.
5
+ #
6
+ # @attr_reader [Arendelle] parsed_questions Parsed Questions from JSON
7
+ # @attr_reader [Arendelle] parsed_response Parsed Answers from JSON
8
+ class FormSubmission
9
+ attr_reader :parsed_questions, :parsed_response
10
+
11
+ # Creates a new Form Submission.
12
+ #
13
+ # @param [Arendelle] parsed_questions Parsed Questions from JSON
14
+ # @param [Arendelle] parsed_response Parsed Answers from JSON
15
+ def initialize(parsed_questions:, parsed_response:)
16
+ @parsed_questions = parsed_questions
17
+ @parsed_response = parsed_response
18
+ end
19
+
20
+ # Builds a full set of Question value objects with answer text.
21
+ # @return [Array<Question>]
22
+ def questions
23
+ @_questions ||= Answers.collate(parsed_response: parsed_response,
24
+ parsed_questions: parsed_questions,
25
+ input_questions: input_questions)
26
+ end
27
+
28
+ private
29
+
30
+ def question_for_grouped(grouped_questions)
31
+ question_texts = grouped_questions.map(&:question)
32
+ return question_texts.first if question_texts.uniq.size == 1
33
+ raise ArgumentError, "Grouped questions do not have matching text"
34
+ end
35
+
36
+ def answerable_questions
37
+ parsed_questions
38
+ .reject { |q| q.id.match /(hidden|legal|statement|group)/ }
39
+ .group_by(&:field_id)
40
+ end
41
+
42
+ def input_questions
43
+ @_input_questions ||= answerable_questions.map do |field_id, grouped_q|
44
+ Question.new(
45
+ ids: grouped_q.map(&:id),
46
+ field_id: field_id,
47
+ original_text: question_for_grouped(grouped_q)
48
+ ).freeze
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ module TypedForm
2
+ module FormData
3
+ # A small class which wraps functionality for parsing JSON data from the
4
+ # Typeform Data API.
5
+ # @attr_reader [String] JSON string
6
+ class ParsedJson
7
+ extend Forwardable
8
+ attr_reader :json
9
+
10
+ # @!method questions
11
+ # @return [Arendelle] parsed_json["questions"] questions data
12
+ # @!method responses
13
+ # @return [Arendelle] parsed_json["responses"] response data
14
+ def_delegators :parsed_json, :questions, :responses
15
+
16
+ # Creates and freezes JSON data.
17
+ # @param [String] json JSON data matching the Typeform Data API format.
18
+ def initialize(json:)
19
+ @json = json.freeze
20
+ end
21
+
22
+ private
23
+
24
+ def parsed_json
25
+ @_parsed_json ||= JSON.parse(json, object_class: Arendelle)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,62 @@
1
+ module TypedForm
2
+ module FormData
3
+ # Question value objects represent the Typeform concept of a question, but
4
+ # expose an interface for querying questions and answers.
5
+ #
6
+ # @attr_reader [Array<Strong>] ids The Typeform IDs for the question. This
7
+ # is ids instead of id because of the way Typeform represents multiple
8
+ # choice questions.
9
+ # @attr_reader [Integer] field_id The Typeform Field ID for the question
10
+ # @attr_reader [String] The original text for the question before answers
11
+ # are extrapolated back into the question.
12
+ # @attr [String] answer The answer of the question.
13
+ # @attr [Text] answer The extrapolated text for the question
14
+ class Question
15
+ attr_reader :ids, :field_id, :original_text
16
+ attr_accessor :answer, :text
17
+
18
+ # Creates a new Question value object, which represents any number of
19
+ # questions in a Typeform Form that can be logically represented as a
20
+ # single question. This includes both single question fields and fields
21
+ # like multiple choice, picture choice, etc.
22
+ #
23
+ # @param [Array<Strong>] ids The Typeform IDs for the question. This
24
+ # is ids instead of id because of the way Typeform represents multiple
25
+ # choice questions.
26
+ # @param [Integer] field_id The Typeform Field ID for the question
27
+ # @param [String] original_text The original text for the question before
28
+ # answers are extrapolated back into the question.
29
+ def initialize(ids:, field_id:, original_text:)
30
+ @ids = ids
31
+ @field_id = field_id
32
+ @original_text = original_text
33
+ end
34
+
35
+ # Performs a regular expression based on the id of the question, to
36
+ # determine the Type of object. This information can be queried in order
37
+ # to allow users to handle various types of Typeform data differently.
38
+ def type
39
+ @_type ||= determine_type
40
+ end
41
+
42
+ # Creates a new Question with existing data from a previous question.
43
+ #
44
+ # @return [Question] Question with extrapolated text in questions and
45
+ # with the answer to the question as an attribute.
46
+ def self.with_response_data(question:, text:, answer:)
47
+ question.dup.tap do |new_question|
48
+ new_question.answer = answer
49
+ new_question.text = text
50
+ end.freeze
51
+ end
52
+
53
+ private
54
+
55
+ def determine_type
56
+ detected_type = ids.map { |id| id.split("_")[0] }.uniq
57
+ return detected_type.first if detected_type.size == 1
58
+ raise StandardError, "Cannot detect type of question ids #{ids}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,4 @@
1
1
  module TypedForm
2
- VERSION = "0.0.4"
2
+ # Up-to-date version of gem.
3
+ VERSION = "0.1.0"
3
4
  end
@@ -0,0 +1,47 @@
1
+ module TypedForm
2
+ # Methods used for handling incoming webhook events with data using the
3
+ # Typeform Webhook JSON schema.
4
+ #
5
+ # @see https://www.typeform.com/help/webhooks/ Typeform Webhook Docs
6
+ #
7
+ # @attr_reader [String] json JSON data from an incoming Typeform Webhook
8
+ class Webhook
9
+ extend Forwardable
10
+
11
+ # @!method form_response
12
+ # @return [Arendelle] An immutable representation of the Webhook JSON
13
+ # form_response field.
14
+ def_delegators :parsed_json, :form_response
15
+
16
+ # @!method form_id
17
+ # @return [String] The form ID from the webhook submission.
18
+ def_delegators :form_response, :form_id
19
+ attr_reader :json
20
+
21
+ # Creates a new webhook object from an incoming Typeform Data stream.
22
+ # @param [String] json JSON Data from a Typeform Webhook
23
+ def initialize(json: json)
24
+ @json = json.freeze
25
+ end
26
+
27
+ # Retrieves the Token from the Webhook JSON data.
28
+ #
29
+ # @return [String] Unique token for the form submission.
30
+ def form_token
31
+ form_response.token
32
+ end
33
+
34
+ # Retrieves the Form ID from the Webhook JSON data.
35
+ #
36
+ # @return [Integer] Typeform Form ID for the Webhook.
37
+ def form_id
38
+ form_response.form_id
39
+ end
40
+
41
+ private
42
+
43
+ def parsed_json
44
+ @_parsed_json ||= JSON.parse(json, object_class: Arendelle)
45
+ end
46
+ end
47
+ end
data/lib/typed_form.rb CHANGED
@@ -1,12 +1,16 @@
1
1
  require "httparty"
2
2
  require "forwardable"
3
3
  require "arendelle"
4
- require "typed_form/client"
5
- require "typed_form/json_response_handler"
6
- require "typed_form/form_response"
7
- require "typed_form/question"
8
- require "typed_form/form_answers"
4
+ require "typed_form/api/client"
5
+ require "typed_form/form_data/parsed_json"
6
+ require "typed_form/form_data/question"
7
+ require "typed_form/form_data/answers"
8
+ require "typed_form/form_data/form_submission"
9
+ require "typed_form/form"
10
+ require "typed_form/webhook"
9
11
  require "typed_form/version"
10
12
 
13
+ # A collection of value objects and utilities for working with data fetched
14
+ # or cached from the Typeform Data API.
11
15
  module TypedForm
12
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed_form
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Cole
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2017-03-10 00:00:00.000000000 Z
12
+ date: 2017-03-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty
@@ -134,12 +134,14 @@ files:
134
134
  - bin/console
135
135
  - bin/setup
136
136
  - lib/typed_form.rb
137
- - lib/typed_form/client.rb
138
- - lib/typed_form/form_answers.rb
139
- - lib/typed_form/form_response.rb
140
- - lib/typed_form/json_response_handler.rb
141
- - lib/typed_form/question.rb
137
+ - lib/typed_form/api/client.rb
138
+ - lib/typed_form/form.rb
139
+ - lib/typed_form/form_data/answers.rb
140
+ - lib/typed_form/form_data/form_submission.rb
141
+ - lib/typed_form/form_data/parsed_json.rb
142
+ - lib/typed_form/form_data/question.rb
142
143
  - lib/typed_form/version.rb
144
+ - lib/typed_form/webhook.rb
143
145
  homepage: https://github.com/useed/typed_form
144
146
  licenses:
145
147
  - MIT
@@ -1,35 +0,0 @@
1
- module TypedForm
2
- class Client
3
- attr_reader :api_key
4
-
5
- include HTTParty
6
-
7
- def initialize(api_key:)
8
- @api_key = api_key
9
- end
10
-
11
- def forms_by_id(form_id:, **query_params)
12
- url_params = query_params.map { |k, v| "#{k}=#{v}" }
13
- request_url = [form_id, authenticated_slug(url_params)].join("?")
14
- get(request_url).body
15
- end
16
-
17
- def find_form_by(form_id:, token:, **query_params)
18
- forms_by_id(form_id: form_id, token: token, **query_params)
19
- end
20
-
21
- private
22
-
23
- def get(slug)
24
- self.class.get(base_url + slug)
25
- end
26
-
27
- def base_url
28
- "https://api.typeform.com/v1/form/"
29
- end
30
-
31
- def authenticated_slug(url_params)
32
- ["key=#{api_key}", url_params].join("&")
33
- end
34
- end
35
- end
@@ -1,66 +0,0 @@
1
- module TypedForm
2
- class FormAnswers
3
- extend Forwardable
4
-
5
- def_delegators :response, :answers, :metadata, :token
6
- def_delegators :metadata, :date_submit
7
-
8
- attr_reader :response, :input_questions, :original_questions
9
-
10
- def self.collate(response:, input_questions:, original_questions:)
11
- new(response: response,
12
- input_questions: input_questions,
13
- original_questions: original_questions).questions
14
- end
15
-
16
- def initialize(response:, input_questions:, original_questions:)
17
- @response = response
18
- @input_questions = input_questions
19
- @original_questions = original_questions
20
- end
21
-
22
- def questions
23
- @_questions ||= build_questions
24
- end
25
-
26
- private
27
-
28
- def build_questions
29
- input_questions.map do |question|
30
- Question.with_response_data(
31
- question: question,
32
- answer: answers_for(question.ids),
33
- text: extrapolated_question_text(question)
34
- )
35
- end
36
- end
37
-
38
- def answers_for(ids)
39
- id_answers = ids.map { |id| find_answer_by_id(id) }.compact
40
- return if id_answers.size.zero?
41
- id_answers.join(", ")
42
- end
43
-
44
- def extrapolated_question_text(question)
45
- regex = %r(\{\{answer_(\d+)\}\})
46
- id_match = question.original_text.match(regex)
47
- return question.original_text unless id_match
48
-
49
- question.original_text.gsub(regex, find_answer_by_field_id(id_match[1]))
50
- end
51
-
52
- def find_answer_by_field_id(id)
53
- fields = original_questions.select do |question|
54
- question.field_id == id.to_i
55
- end
56
-
57
- answers_found = fields.map { |field| find_answer_by_id(field.id) }.compact
58
- return find_answer_by_id(fields.first.id) if answers_found.size == 1
59
- raise ArgumentError, "Cannot find single answer with field ID ##{id}"
60
- end
61
-
62
- def find_answer_by_id(id)
63
- answers.instance_variable_get("@#{id}")
64
- end
65
- end
66
- end
@@ -1,52 +0,0 @@
1
- module TypedForm
2
- class FormResponse
3
- attr_reader :parsed_questions, :parsed_response
4
-
5
- def initialize(parsed_questions:, parsed_response:)
6
- @parsed_questions = parsed_questions
7
- @parsed_response = parsed_response
8
- end
9
-
10
- def questions_and_answers
11
- FormAnswers.collate(response: parsed_response,
12
- input_questions: questions,
13
- original_questions: parsed_questions)
14
- end
15
-
16
- def questions
17
- @_questions ||= build_questions
18
- end
19
-
20
- def question_ids
21
- questions.flat_map(&:ids)
22
- end
23
-
24
- def question_texts
25
- questions.map(&:original_text).uniq
26
- end
27
-
28
- private
29
-
30
- def question_for_grouped(grouped_questions)
31
- question_texts = grouped_questions.map(&:question)
32
- return question_texts.first if question_texts.uniq.size == 1
33
- raise ArgumentError, "Grouped questions do not have matching text"
34
- end
35
-
36
- def answerable_questions
37
- parsed_questions
38
- .reject { |q| q.id.match /(hidden|legal|statement|group)/ }
39
- .group_by(&:field_id)
40
- end
41
-
42
- def build_questions
43
- answerable_questions.map do |field_id, grouped_questions|
44
- Question.new(
45
- ids: grouped_questions.map(&:id),
46
- field_id: field_id,
47
- original_text: question_for_grouped(grouped_questions)
48
- ).freeze
49
- end
50
- end
51
- end
52
- end
@@ -1,16 +0,0 @@
1
- module TypedForm
2
- class JSONResponseHandler
3
- extend Forwardable
4
- attr_reader :json
5
-
6
- def_delegators :parsed_json, :questions, :responses
7
-
8
- def initialize(json)
9
- @json = json
10
- end
11
-
12
- def parsed_json
13
- @_parsed_json ||= JSON.parse(json, object_class: Arendelle)
14
- end
15
- end
16
- end
@@ -1,42 +0,0 @@
1
- module TypedForm
2
- class Question
3
- attr_reader :ids, :field_id, :original_text
4
- attr_accessor :answer, :text
5
-
6
- def initialize(ids:, field_id:, original_text:)
7
- @ids = ids
8
- @field_id = field_id
9
- @original_text = original_text
10
- end
11
-
12
- def add_response_data(answer:, text:)
13
- @answer = answer
14
- @text = text
15
- end
16
-
17
- def type
18
- @_type ||= determine_type
19
- end
20
-
21
- def type_for_grouped(grouped_questions)
22
- types = questions.map(&:type)
23
- return types.first if types.uniq.size == 1
24
- raise ArgumentError, "Grouped questions do not have matching types"
25
- end
26
-
27
- def self.with_response_data(question:, text:, answer:)
28
- question.dup.tap do |new_question|
29
- new_question.answer = answer
30
- new_question.text = text
31
- end.freeze
32
- end
33
-
34
- private
35
-
36
- def determine_type
37
- detected_type = ids.map { |id| id.split("_")[0] }.uniq
38
- return detected_type.first if detected_type.size == 1
39
- raise StandardError, "Cannot detect type of question ids #{ids}"
40
- end
41
- end
42
- end