typed_form 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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