typeform_data 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +66 -26
- data/lib/typeform_data.rb +1 -0
- data/lib/typeform_data/comparable_by_id_and_config.rb +20 -0
- data/lib/typeform_data/typeform.rb +20 -14
- data/lib/typeform_data/typeform/answer.rb +48 -12
- data/lib/typeform_data/typeform/field.rb +20 -7
- data/lib/typeform_data/typeform/question.rb +16 -2
- data/lib/typeform_data/typeform/response.rb +10 -21
- data/lib/typeform_data/version.rb +1 -1
- data/typeform_data.gemspec +5 -7
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f66614378d50f785ebb54f8e8739bb30ddbc5f48
|
4
|
+
data.tar.gz: 91ef3fe46c067e7f11f5a503e6dc099ce1fe28d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02d193b05183e53f3774df96873d6847e353b6f98e3042bd5344f9da37bf5a493e7e6a3c5eb6520646d6d54d8202aff81db045dfb91c0c065630e36c6e12f623
|
7
|
+
data.tar.gz: 24f0f010677b83ca46d8b547ec11f66ec6cc95229789fafc01eb142bb9dd95d21e41bfb5beb383125af74e9e63e8f07f72bebf50262b4d8a06de93252518dc36
|
data/README.md
CHANGED
@@ -1,27 +1,74 @@
|
|
1
1
|
# TypeformData
|
2
2
|
|
3
|
-
A Ruby client for Typeform's Data API
|
3
|
+
A Ruby client for Typeform's [Data API](https://www.typeform.com/help/data-api/).
|
4
4
|
|
5
|
-
|
5
|
+
**Warning**: this is alpha software, and hasn't been thoroughly vetted in production yet. Use at your own risk :).
|
6
6
|
|
7
|
-
|
7
|
+
## Usage:
|
8
8
|
|
9
|
-
|
9
|
+
```
|
10
|
+
client = TypeformData::Client.new(api_key: 'YOUR_API_KEY')
|
11
|
+
typeforms = client.all_typeforms
|
12
|
+
|
13
|
+
typeform = typeforms.first
|
14
|
+
=> #<TypeformData::Typeform
|
15
|
+
@config=#<TypeformData::Config @api_key="YOUR_API_KEY">,
|
16
|
+
@id="TYPEFORM_ID",
|
17
|
+
@name="TYPEFORM_NAME">
|
18
|
+
```
|
19
|
+
|
20
|
+
### Fetching responses
|
21
|
+
|
22
|
+
```
|
23
|
+
all_complete_responses = typeform.responses(completed: true)
|
24
|
+
```
|
10
25
|
|
11
|
-
|
12
|
-
- Add more detail, and example method calls.
|
13
|
-
- Add an explanation: why another gem? What makes this gem different?
|
26
|
+
Unless you specify a limit, `TypeformData::Typeform#responses` will not paginate, and will make multiple AJAX requests as needed (the Data API only returns up to 1000 responses at a time) to fetch all the matching responses.
|
14
27
|
|
15
|
-
|
16
|
-
- Typeform data (and invidual response data) must work with `Marshal#load` and `Marshal#dump`.
|
28
|
+
You can also specify any of the ["Filtering Options"](https://www.typeform.com/help/data-api/) to pass along to the API call:
|
17
29
|
|
18
|
-
|
30
|
+
(*Warning*: the `token` parameter isn't working yet.)
|
19
31
|
|
20
|
-
|
32
|
+
```
|
33
|
+
some_complete_responses = typeform.responses(limit: 500, offset: 2000, completed: true)
|
34
|
+
two_days_of_responses = typeform.responses(from: 1470143917, since: 1470316722)
|
21
35
|
|
22
|
-
|
36
|
+
```
|
23
37
|
|
24
|
-
|
38
|
+
### Questions & answers
|
39
|
+
|
40
|
+
The response data you get back is represented using classes with defined relationships:
|
41
|
+
|
42
|
+
```
|
43
|
+
typeform.responses.first.answers.first.typeform == typeform
|
44
|
+
=> true
|
45
|
+
|
46
|
+
typeform.fields.map(&:text)
|
47
|
+
=> ["What is your name?", "What are your favorite colors?", ...]
|
48
|
+
|
49
|
+
typeform.responses.first.answers.map { |answer| [answer.field_text, answer.value] }`
|
50
|
+
=> [["What is your name?", "Foo Bar"], ["What are your favorite colors?", ["blue", "orange"]]]
|
51
|
+
|
52
|
+
```
|
53
|
+
|
54
|
+
To access a Typeform's questions, we recommend using `TypeformData::Typeform#fields` instead of `TypeformData::Typeform#questions`. Each `TypeformData::Typeform::Answer` is associated to exactly one `TypeformData::Typeform::Field`, and one or more `TypeformData::Typeform::Question`s.
|
55
|
+
|
56
|
+
## Notes on the API
|
57
|
+
|
58
|
+
So far, we've found Typeform's current Data API to be confusing. In particular, there are a couple design decisions that have been a big source of confusion and friction for us:
|
59
|
+
|
60
|
+
- Statements (which are sections of text in a Typeform, and can't be answered) and Hidden Fields (data passed into a form, and not provided by the user) are both included under the `'questions'` key in the API's response JSON. From the perspective of a user, we don't think of these as "questions".
|
61
|
+
- Each option in a "Picture choice" (and, IIRC "Multiple choice" as well, if multiple choices are allowed) is returned as its own "question" in the response JSON for questions and answers. We feel that it makes more sense to model these as multiple answers to one question, i.e. an Array-valued answer.
|
62
|
+
|
63
|
+
The main goal of this API wrapper is to encapsulate these implementation details (which we find confusing) and provide a more intuitive API for our application code. This means that our data model must deviating in specific places from the implicit data model expressed in the Data API's JSON responses. We're sacrificing consistency for a more intuitive client API.
|
64
|
+
|
65
|
+
## Notes
|
66
|
+
|
67
|
+
- We haven't tested against any Ruby versions other than 2.3.
|
68
|
+
- At the moment, this gem has no runtime dependencies.
|
69
|
+
- Under the hood, the object relationships are implemented by storing a reference to a config object containing your API key. This is what allows you to say `answer.typeform.responses` and make an API call originating from a `TypeformData::Typeform::Answer` without having to pass in a reference to a client or your API key (again). To avoid leaking your API key, make sure to clear out the `@config` reference if you serialize any of the objects! We've already done a bit here: if you call `Marshal.dump` on a `TypeformData::ValueClass`, we only serialize attributes (not references, and not the `@config` object).
|
70
|
+
|
71
|
+
### Installation
|
25
72
|
|
26
73
|
Add this line to your application's Gemfile:
|
27
74
|
|
@@ -33,25 +80,18 @@ And then execute:
|
|
33
80
|
|
34
81
|
$ bundle
|
35
82
|
|
36
|
-
|
37
|
-
|
38
|
-
$ gem install typeform_data
|
39
|
-
|
40
|
-
## Usage
|
41
|
-
|
42
|
-
TODO: Write usage instructions here
|
43
|
-
|
44
|
-
## Development
|
83
|
+
### Development
|
45
84
|
|
46
85
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
47
86
|
|
48
|
-
|
87
|
+
### Releasing a new version
|
49
88
|
|
50
|
-
|
89
|
+
Update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
51
90
|
|
52
|
-
|
91
|
+
### Contributing
|
53
92
|
|
93
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/typeform_data. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
54
94
|
|
55
|
-
|
95
|
+
### License
|
56
96
|
|
57
97
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/typeform_data.rb
CHANGED
@@ -31,6 +31,7 @@ require 'typeform_data/client'
|
|
31
31
|
require 'typeform_data/errors'
|
32
32
|
require 'typeform_data/config'
|
33
33
|
require 'typeform_data/value_class'
|
34
|
+
require 'typeform_data/comparable_by_id_and_config'
|
34
35
|
|
35
36
|
require 'typeform_data/requestor'
|
36
37
|
require 'typeform_data/api_response'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TypeformData
|
3
|
+
module ComparableByIdAndConfig
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
# Override this method to specify a different key.
|
7
|
+
def sort_key
|
8
|
+
id
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
other.sort_key == sort_key && other.config == config
|
13
|
+
end
|
14
|
+
|
15
|
+
def <=>(other)
|
16
|
+
other.sort_key <=> sort_key
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -5,10 +5,9 @@ module TypeformData
|
|
5
5
|
|
6
6
|
class Typeform
|
7
7
|
include TypeformData::ValueClass
|
8
|
+
include TypeformData::ComparableByIdAndConfig
|
8
9
|
readable_attributes :id, :name
|
9
10
|
|
10
|
-
# TODO: define comparison methods <=>, etc.
|
11
|
-
|
12
11
|
# See https://www.typeform.com/help/data-api/ under "Filtering Options" for the full list of
|
13
12
|
# options.
|
14
13
|
#
|
@@ -23,8 +22,6 @@ module TypeformData
|
|
23
22
|
# @param Hash<[String, Symbol], [String, Symbol]> params
|
24
23
|
# @raise TypeformData::ArgumentError
|
25
24
|
def responses(params = {})
|
26
|
-
# TODO: not sure what the implementation will be here, since responses_request needs to
|
27
|
-
# handle the awkwardness of returning multiple kinds of data at the same time.
|
28
25
|
response = responses_request(collapse_and_validate_responses_params(params))
|
29
26
|
set_stats(response['stats']['responses'])
|
30
27
|
|
@@ -37,28 +34,37 @@ module TypeformData
|
|
37
34
|
}
|
38
35
|
end
|
39
36
|
|
37
|
+
# Typeform's 'question' concept (as expressed in the API) has the following disadvantages:
|
38
|
+
# - Each choice in a multi-select is treated as its own 'question'
|
39
|
+
# - Hidden Fields are included as 'questions'
|
40
|
+
# - Statements are included as 'questions'
|
41
|
+
#
|
42
|
+
# In practice, I recommend using TypeformData::Typeform#field instead, as it addresses these
|
43
|
+
# issues. Typeform#quesions is here so you have access to the underlying data if you need it.
|
44
|
+
#
|
45
|
+
# @return [TypeformData::Typeform::Question]
|
46
|
+
def questions
|
47
|
+
@_questions ||= fetch_questions
|
48
|
+
end
|
49
|
+
|
40
50
|
def fields
|
41
51
|
@_fields ||= Field.from_questions(config, questions)
|
42
52
|
end
|
43
53
|
|
44
|
-
|
45
|
-
|
46
|
-
def questions
|
47
|
-
(@_questions ||= fetch_questions).reject(&:hidden_field?)
|
54
|
+
def hidden_fields
|
55
|
+
questions.select(&:hidden_field?)
|
48
56
|
end
|
49
57
|
|
50
|
-
def
|
51
|
-
|
58
|
+
def statements
|
59
|
+
questions.select(&:statement?)
|
52
60
|
end
|
53
61
|
|
54
62
|
def stats
|
55
63
|
@_stats ||= fetch_stats
|
56
64
|
end
|
57
65
|
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
|
66
|
+
# This method will make an AJAX request if this Typeform's name hasn't already been set.
|
67
|
+
# @return [String]
|
62
68
|
def name
|
63
69
|
return name if name
|
64
70
|
@name ||= client.all_typeforms.find { |typeform| typeform.id == id }.name
|
@@ -5,23 +5,20 @@ module TypeformData
|
|
5
5
|
class Answer
|
6
6
|
include TypeformData::ValueClass
|
7
7
|
include TypeformData::Typeform::ById
|
8
|
+
include TypeformData::ComparableByIdAndConfig
|
9
|
+
|
10
|
+
def sort_key
|
11
|
+
field_id
|
12
|
+
end
|
8
13
|
|
9
14
|
# field_text may be removed in the future: we may want to normalize our data model. For
|
10
15
|
# now, it's quite convenient to have.
|
11
|
-
readable_attributes :id, :value, :field_text, :response_token, :typeform_id
|
12
|
-
|
13
|
-
# IDs are of the form:
|
14
|
-
#
|
15
|
-
# - "textfield_12316024"
|
16
|
-
# - "listimage_12316029_choice_12322262"
|
17
16
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
|
21
|
-
id.split('_')[1]
|
22
|
-
end
|
17
|
+
# The type of 'value' is [String, Fixnum, Array<String>, Array<Fixnum>], since we are
|
18
|
+
# combining 'answers' from the API's JSON responses if those answers share the same field.
|
19
|
+
readable_attributes :field_id, :value, :field_text, :response_token, :typeform_id
|
23
20
|
|
24
|
-
def
|
21
|
+
def field_type
|
25
22
|
id.split('_').first
|
26
23
|
end
|
27
24
|
|
@@ -30,6 +27,45 @@ module TypeformData
|
|
30
27
|
# def response
|
31
28
|
# typeform.responses(token: response_token)
|
32
29
|
# end
|
30
|
+
|
31
|
+
# In the JSON, answer 'ID's are of the form:
|
32
|
+
#
|
33
|
+
# - "textfield_12316024"
|
34
|
+
# - "listimage_12316029_choice_12322262"
|
35
|
+
#
|
36
|
+
# This list may not be exhaustive-- there may be other ID formats not covererd above-- since
|
37
|
+
# this part of the API isn't mentioned in the documentation.
|
38
|
+
#
|
39
|
+
# For our Answer object, we strip out only the 'listimage_12316029' part, giving Answers the
|
40
|
+
# same specificity as Fields.
|
41
|
+
#
|
42
|
+
# Use this method to create Answers when initializing a Response.
|
43
|
+
# @return [Array<Answer>]
|
44
|
+
def self.from_response_attrs(config, attrs, fields)
|
45
|
+
(attrs[:answers] || attrs['answers']).group_by { |id, _value|
|
46
|
+
field_id = id.split('_')[1]
|
47
|
+
|
48
|
+
unless field_id && field_id.length.positive?
|
49
|
+
raise UnexpectedError, 'Falsy field ID for answer(s)'
|
50
|
+
end
|
51
|
+
|
52
|
+
fields.find { |field| field.id.to_s == field_id }.tap { |matched|
|
53
|
+
raise UnexpectedError, 'Expected to find a matching field' unless matched
|
54
|
+
}
|
55
|
+
}.map { |field, ids_and_values|
|
56
|
+
values = ids_and_values.map(&:last)
|
57
|
+
|
58
|
+
Answer.new(
|
59
|
+
config,
|
60
|
+
field_id: field.id,
|
61
|
+
value: values.one? ? values.first : values,
|
62
|
+
field_text: field.text,
|
63
|
+
response_token: attrs[:token] || attrs['token'],
|
64
|
+
typeform_id: attrs[:typeform_id] || attrs['typeform_id'],
|
65
|
+
)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
33
69
|
end
|
34
70
|
|
35
71
|
end
|
@@ -5,22 +5,35 @@ module TypeformData
|
|
5
5
|
class Field
|
6
6
|
include TypeformData::ValueClass
|
7
7
|
include TypeformData::Typeform::ById
|
8
|
-
|
8
|
+
include TypeformData::ComparableByIdAndConfig
|
9
|
+
readable_attributes :id, :type, :text, :typeform_id, :question_ids
|
9
10
|
|
10
11
|
# The Data API includes 'statements' as part of a Typeform's "questions", despite the fact
|
11
12
|
# that these statements don't have associated answers.
|
12
13
|
def statement?
|
13
|
-
|
14
|
+
type == 'statement'
|
14
15
|
end
|
15
16
|
|
16
17
|
def self.from_questions(config, input_questions)
|
17
|
-
input_questions.
|
18
|
-
|
19
|
-
|
18
|
+
input_questions.reject(
|
19
|
+
&:hidden_field?
|
20
|
+
).reject(
|
21
|
+
&:statement?
|
22
|
+
).group_by(&:field_id).map do |field_id, questions|
|
23
|
+
unless 1 == questions.map(&:text).uniq.length && 1 == questions.map(&:type).uniq.length
|
24
|
+
# TODO: turn these errors into warnings.
|
25
|
+
raise UnexpectedError, 'Expected questions with the same field_id to have the same '\
|
26
|
+
'type and text'
|
20
27
|
end
|
21
28
|
|
22
|
-
new(
|
23
|
-
|
29
|
+
new(
|
30
|
+
config,
|
31
|
+
id: field_id,
|
32
|
+
type: questions.first.type,
|
33
|
+
text: questions.first.text,
|
34
|
+
question_ids: questions.map(&:id),
|
35
|
+
typeform_id: questions.first.typeform_id,
|
36
|
+
)
|
24
37
|
end
|
25
38
|
end
|
26
39
|
|
@@ -5,19 +5,33 @@ module TypeformData
|
|
5
5
|
class Question
|
6
6
|
include TypeformData::ValueClass
|
7
7
|
include TypeformData::Typeform::ById
|
8
|
+
include TypeformData::ComparableByIdAndConfig
|
9
|
+
|
10
|
+
# A question ID is of the form:
|
11
|
+
# - opinionscale_20576123
|
12
|
+
# - listimage_46576029_choice_26422755
|
13
|
+
#
|
14
|
+
# It looks like the second part of the ID is the field ID, but the Data API includes
|
15
|
+
# 'field_id' as a separate property in the response JSON, so I'm not sure if we can treat
|
16
|
+
# them as the same.
|
17
|
+
#
|
8
18
|
readable_attributes :id, :question, :field_id, :typeform_id
|
9
19
|
|
10
20
|
# Question#question makes for a bad API. Ideally, use Question#text instead.
|
11
21
|
alias text question
|
12
22
|
|
23
|
+
def type
|
24
|
+
id.split('_').first
|
25
|
+
end
|
26
|
+
|
13
27
|
def hidden_field?
|
14
|
-
|
28
|
+
type == 'hidden'
|
15
29
|
end
|
16
30
|
|
17
31
|
# The Data API includes 'statements' as part of a Typeform's "questions", despite the fact
|
18
32
|
# that these statements don't have associated answers.
|
19
33
|
def statement?
|
20
|
-
|
34
|
+
type == 'statement'
|
21
35
|
end
|
22
36
|
end
|
23
37
|
|
@@ -5,8 +5,16 @@ module TypeformData
|
|
5
5
|
class Response
|
6
6
|
include TypeformData::ValueClass
|
7
7
|
include TypeformData::Typeform::ById
|
8
|
+
include TypeformData::ComparableByIdAndConfig
|
9
|
+
|
8
10
|
readable_attributes :token, :metadata, :hidden, :typeform_id, :answers, :completed
|
9
11
|
|
12
|
+
alias hidden_fields hidden
|
13
|
+
|
14
|
+
def sort_key
|
15
|
+
token
|
16
|
+
end
|
17
|
+
|
10
18
|
# It's correct to name this attribute "completed?" and not "complete?" since it's always in
|
11
19
|
# the past tense-- once a potential respondent leaves a Typeform unsubmitted, they can never
|
12
20
|
# go back and complete it.
|
@@ -14,29 +22,14 @@ module TypeformData
|
|
14
22
|
@completed == 1
|
15
23
|
end
|
16
24
|
|
17
|
-
alias hidden_fields hidden
|
18
|
-
|
19
25
|
def date_submitted
|
20
26
|
DateTime.strptime(metadata['date_submit'], '%Y-%m-%d %H:%M:%S')
|
21
27
|
end
|
22
28
|
|
23
29
|
def initialize(config, attrs, fields)
|
24
30
|
mapped_attrs = attrs.dup
|
25
|
-
|
26
|
-
mapped_attrs
|
27
|
-
matching_field = fields.find { |field| field.id.to_s == id.split('_')[1] }
|
28
|
-
raise UnexpectedError, 'Expected to find a matching field' unless matching_field
|
29
|
-
|
30
|
-
Answer.new(
|
31
|
-
config,
|
32
|
-
id: id,
|
33
|
-
value: value,
|
34
|
-
field_text: matching_field.text,
|
35
|
-
response_token: attrs[:token] || attrs['token'],
|
36
|
-
typeform_id: attrs[:typeform_id] || attrs['typeform_id'],
|
37
|
-
)
|
38
|
-
}
|
39
|
-
|
31
|
+
mapped_attrs[:answers] = Answer.from_response_attrs(config, attrs, fields)
|
32
|
+
mapped_attrs.delete('answers')
|
40
33
|
super(config, mapped_attrs)
|
41
34
|
end
|
42
35
|
|
@@ -45,10 +38,6 @@ module TypeformData
|
|
45
38
|
answers.each { |answer| answer.config = config }
|
46
39
|
end
|
47
40
|
|
48
|
-
def ==(other)
|
49
|
-
other.token == token && other.config == config
|
50
|
-
end
|
51
|
-
|
52
41
|
end
|
53
42
|
|
54
43
|
end
|
data/typeform_data.gemspec
CHANGED
@@ -9,15 +9,13 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.name = 'typeform_data'
|
10
10
|
spec.version = TypeformData::VERSION
|
11
11
|
spec.authors = ['Max Wallace']
|
12
|
-
spec.email = ['
|
12
|
+
spec.email = ['engineering@shearwaterintl.com']
|
13
13
|
|
14
|
-
spec.summary = 'An opinionated client for the Typeform.com Data API'
|
15
|
-
spec.description = 'typeform_data is a minimal, opinionated client for the Typeform.com
|
16
|
-
'API
|
17
|
-
'project is to create a maintainable, extensible client that provides a '\
|
18
|
-
"more natural object-oriented interface to Typeform.com's Data API."
|
14
|
+
spec.summary = 'An opinionated, OO client for the Typeform.com Data API'
|
15
|
+
spec.description = 'typeform_data is a minimal, opinionated, OO client for the Typeform.com '\
|
16
|
+
'Data API with no runtime dependencies.'
|
19
17
|
|
20
|
-
spec.homepage = 'https://github.com/
|
18
|
+
spec.homepage = 'https://github.com/shearwaterintl/typeform_data'
|
21
19
|
spec.license = 'MIT'
|
22
20
|
|
23
21
|
spec.files = `git ls-files -z`.split("\x0").reject { |f|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typeform_data
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Max Wallace
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -136,12 +136,10 @@ dependencies:
|
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0.39'
|
139
|
-
description: typeform_data is a minimal, opinionated client for the Typeform.com
|
140
|
-
API
|
141
|
-
create a maintainable, extensible client that provides a more natural object-oriented
|
142
|
-
interface to Typeform.com's Data API.
|
139
|
+
description: typeform_data is a minimal, opinionated, OO client for the Typeform.com
|
140
|
+
Data API with no runtime dependencies.
|
143
141
|
email:
|
144
|
-
-
|
142
|
+
- engineering@shearwaterintl.com
|
145
143
|
executables: []
|
146
144
|
extensions: []
|
147
145
|
extra_rdoc_files: []
|
@@ -160,6 +158,7 @@ files:
|
|
160
158
|
- lib/typeform_data.rb
|
161
159
|
- lib/typeform_data/api_response.rb
|
162
160
|
- lib/typeform_data/client.rb
|
161
|
+
- lib/typeform_data/comparable_by_id_and_config.rb
|
163
162
|
- lib/typeform_data/config.rb
|
164
163
|
- lib/typeform_data/errors.rb
|
165
164
|
- lib/typeform_data/requestor.rb
|
@@ -173,7 +172,7 @@ files:
|
|
173
172
|
- lib/typeform_data/value_class.rb
|
174
173
|
- lib/typeform_data/version.rb
|
175
174
|
- typeform_data.gemspec
|
176
|
-
homepage: https://github.com/
|
175
|
+
homepage: https://github.com/shearwaterintl/typeform_data
|
177
176
|
licenses:
|
178
177
|
- MIT
|
179
178
|
metadata: {}
|
@@ -196,5 +195,5 @@ rubyforge_project:
|
|
196
195
|
rubygems_version: 2.5.1
|
197
196
|
signing_key:
|
198
197
|
specification_version: 4
|
199
|
-
summary: An opinionated client for the Typeform.com Data API
|
198
|
+
summary: An opinionated, OO client for the Typeform.com Data API
|
200
199
|
test_files: []
|