├── .gitignore
├── .rspec
├── .ruby-version
├── .travis.yml
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── ask_awesomely.gemspec
├── bin
├── console
└── setup
├── lib
├── ask_awesomely.rb
└── ask_awesomely
│ ├── api_client.rb
│ ├── choice.rb
│ ├── configuration.rb
│ ├── design.rb
│ ├── dsl.rb
│ ├── embeddable.rb
│ ├── embeds
│ ├── drawer.erb
│ ├── fullscreen.erb
│ ├── modal.erb
│ └── widget.erb
│ ├── field.rb
│ ├── field
│ ├── dropdown.rb
│ ├── email.rb
│ ├── field.rb
│ ├── legal.rb
│ ├── long_text.rb
│ ├── multiple_choice.rb
│ ├── number.rb
│ ├── opinion_scale.rb
│ ├── picture_choice.rb
│ ├── rating.rb
│ ├── short_text.rb
│ ├── statement.rb
│ ├── website.rb
│ └── yes_no.rb
│ ├── json_builder.rb
│ ├── logic_jump.rb
│ ├── picture.rb
│ ├── s3.rb
│ ├── typeform.rb
│ └── version.rb
└── spec
├── fixtures
├── basic_form.json
├── design_form.json
├── logic_form.json
├── multiple_choice_form.json
├── picture_form.json
├── user_form.json
└── vcr_cassettes
│ ├── create_design.yml
│ ├── design_uploads.yml
│ ├── get_api_info.yml
│ ├── logic_jumps.yml
│ ├── picture_uploads.yml
│ └── submit_basic_typeform.yml
├── lib
└── ask_awesomely
│ ├── api_client_spec.rb
│ ├── configuration_spec.rb
│ ├── design_spec.rb
│ ├── dsl_spec.rb
│ ├── embeddable_spec.rb
│ ├── field
│ └── field_spec.rb
│ ├── field_spec.rb
│ └── picture_spec.rb
├── spec_helper.rb
└── support
├── basic_typeform.rb
├── design_typeform.rb
├── logic_typeform.rb
├── multiple_choice_typeform.rb
├── picture_typeform.rb
└── user_typeform.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /doc/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 | vendor/
11 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.3.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.2.2
4 | - 2.2.3
5 | - 2.3.1
6 | env:
7 | global:
8 | secure: IiOYP2G1Ufie+zMYz/l/+wnLwej6REZCKgFN+hNUU5//0UugDMQHbVHSLPSEuAu3g8T/9CcPgYLVYcTrsZC9Hn/TtB3Sc8w3OuSELD1prsanMlySjTHDTLmZynmDT/IFWxz2mdKIaFwHKvbipJw+VUxyTiFD/Iu/Ns/goM4+HBemxh2cuomAqPIo2qTKWxl9hpcT3YFf9ylbLKFe6LfnRCygeTK+nY3ooZMP+vC5tnYmLX74FINZKnliG8npNXG0fc7X/W5zzy0vKHGKobirxTqgMERZH1voGCShoVKtH9YISgr9g+JJ8AvVHVr58mjc3U26tChl6MKxMYIc+iscX/aoUEPQ6yPgST+Wj7+ZcFduFg/C3xgs0BoNRsJDLDD37a9uyEJet7iusMARs+X+buimRdEUvddkEdW4SNj76Jqjwsgp09Isl5LOLwUB+xsCE9s6CK3pOOsPHVnultFILsSk86Cre2+E2QEukt4r1cghn+Lf9+sdRwOHnBUT6G3p5S1t313JWNfZGx3auFEXacK5jmDjvqWH6QPUfVZJ25XGlHkVhsv4q09/dJcyr//tPsjD6Yrx2hc+pkIu24o8RPQlmpgTTpb/A3a2xaJOaP+PNJB0yT3LAxabEmvsCZCmmhScScagxTCdlBpP43q/Bjr2RA5Mbd+LVRBh7t/90RI=
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in ask_awesomely.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Lee Machin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ask Awesomely
2 |
3 | Build Typeforms awesomely. In Ruby.
4 |
5 | ------
6 | **Sorry :( Typeform IO is closed to new accounts. This library will soon become non-functional and will no longer be maintained or supported.**
7 | ------
8 |
9 | [](http://badge.fury.io/rb/ask_awesomely) [](https://travis-ci.org/leemachin/ask_awesomely)
10 | [](https://codeclimate.com/github/leemachin/ask_awesomely)
11 |
12 |
13 |
14 | ## Table of Contents
15 |
16 | - [Before you start](#before-you-start)
17 | - [Installation](#installation)
18 | - [Usage](#usage)
19 | - [Authentication](#authentication)
20 | - [If you're using images](#if-youre-using-images)
21 | - [Logging](#logging)
22 | - [Basic example](#basic-example)
23 | - [Available fields and options](#available-fields-and-options)
24 | - [Statement](#statement)
25 | - [Short text](#short-text)
26 | - [Long text](#long-text)
27 | - [Multiple choice](#multiple-choice)
28 | - [Picture choice](#picture-choice)
29 | - [Dropdown](#dropdown)
30 | - [Yes/No](#yesno)
31 | - [Number](#number)
32 | - [Rating](#rating)
33 | - [Opinion Scale](#opinion-scale)
34 | - [Email](#email)
35 | - [Website](#website)
36 | - [Legal](#legal)
37 | - [Custom Designs](#custom-designs)
38 | - [Logic Jumps](#logic-jumps)
39 | - [Conditional Fields](#conditional-fields)
40 | - [Common Customisations](#common-customisations)
41 | - [Passing Context](#passing-context)
42 | - [Rendering the Typeform](#rendering-the-typeform)
43 | - [Getting the URL](#getting-the-url)
44 | - [Embedding](#embedding)
45 | - [Modal](#modal)
46 | - [Widget](#widget)
47 | - [Drawer](#drawer)
48 | - [Fullscreen](#fullscreen)
49 | - [Getting Results](#getting-results)
50 | - [Development](#development)
51 | - [Feedback and Contributions](#feedback-and-contributions)
52 |
53 |
54 |
55 | ## Before you start
56 |
57 |
58 | ## Installation
59 |
60 | Prepare your best [Jamie Oliver](https://en.wikipedia.org/wiki/Jamie_Oliver) impression and bang this in yer Gemfile:
61 |
62 | ```ruby
63 | gem 'ask_awesomely'
64 | ```
65 |
66 | Turn your CPU up to 80ºC and let it simmer for a while with this:
67 |
68 | ```shell
69 | bundle install
70 | ```
71 |
72 | Or install it yourself as:
73 |
74 | ```shell
75 | gem install ask_awesomely
76 | ```
77 |
78 | Then include it in your code like this:
79 |
80 | ```ruby
81 | require "ask_awesomely"
82 | ```
83 |
84 | ## Usage
85 |
86 | ### Authentication
87 |
88 | Firstly, you will need to be able to authenticate:
89 |
90 | ```ruby
91 | AskAwesomely.configure do |config|
92 | config.typeform_api_key = ENV["YOUR_TYPEFORM_IO_API_KEY"]
93 | end
94 | ```
95 |
96 | If you don't already have a key, [sign up for one here](https://io1.typeform.com/to/HMLOBl?v=1).
97 |
98 |
99 | Your API Keys are **super secret** so don't commit them in your code. Use `ENV` or
100 | something like [dotenv](https://github.com/bkeepers/dotenv) so you can keep the credentials out of the repository. This stops bad people from stealing the key and hijacking your Typeform I/O account.
101 |
102 | ### If you're using images
103 |
104 | It's possible to create questions that have images (or pictures, as we call them) attached. In fact, one field type relies on this!
105 |
106 | Currently Typeform I/O is only able to accept a URL to an image, which means that any images you use have to be uploaded elsewhere first.
107 |
108 | **If you already handle image uploads in your app** (for example, with [Dragonfly](https://github.com/markevans/dragonfly)), you're okay.
109 |
110 | **If you don't**, you will need to give `AskAwesomely` your AWS credentials so it can do all of the heavy lifting for you.
111 |
112 | ```ruby
113 | AskAwesomely.configure do |config|
114 | config.aws_access_key_id = ENV["YOUR_AWS_ACCESS_KEY_ID"]
115 | config.aws_access_key_secret = ENV["YOUR_AWS_ACCESS_KEY_SECRET"]
116 | end
117 | ```
118 |
119 | As before, don't commit these keys to your repo unless you want [bad things to happen](http://vertis.io/2013/12/16/unauthorised-litecoin-mining.html). Check up on the [AWS Best Practices](http://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html) if you want to know more.
120 |
121 | ### Logging
122 |
123 | `AskAwesomely` might warn you if you miss something out, just to make sure. By default this will go straight to `STDOUT`, but you can tell it to use your own logging:
124 |
125 | ```ruby
126 | AskAwesomely.configure do |config|
127 | config.logger = your_new_logger
128 | end
129 | ```
130 |
131 | ### Basic example
132 |
133 | You will want to create a class that represents a specific form to be built:
134 |
135 | ```ruby
136 | class MyNewTypeform
137 |
138 | include AskAwesomely::DSL
139 |
140 | title "My New Typeform"
141 |
142 | tags "awesome", "hehe"
143 |
144 | field :statement do
145 | say ->(user) { "Hello, #{user.name}!" }
146 | end
147 |
148 | field :multiple_choice do
149 | ask "What is your favourite language?"
150 | choice "Ruby"
151 | choice "Python"
152 | choice "Javascript"
153 | choice "COBOL"
154 |
155 | can_specify_other
156 | end
157 |
158 | end
159 | ```
160 |
161 | After that, it's simply a matter of calling `build` on the class:
162 |
163 | ```ruby
164 | user = OpenStruct.new(name: "Rubyist")
165 | typeform = MyNewTypeform.build(user)
166 | ```
167 |
168 | Check out [Typeform I/O](https://typeform.io) for detailed information about the API, and how to get your API key.
169 |
170 |
171 | ## Available fields and options
172 |
173 | Each field has unique properties. Here are the fields you can use, and the extra
174 | things you can do to customise them.
175 |
176 | Note that some options might not yet be available on [Typeform I/O](https://typeform.io).
177 |
178 | Also note that some field types and customisations that are available on [Typeform.com](https://typeform.com) may not be available on [Typeform I/O](https://typeform.io).
179 |
180 |
181 | ### Statement
182 |
183 | A block of text that isn't a question and requires no answer.
184 |
185 | ```ruby
186 | field :statement do
187 | say "what you want to say"
188 | button_text "Okay, next question"
189 | no_quotation_marks
190 | end
191 | ```
192 |
193 | ### Short text
194 |
195 | A question where the answer is a short amount of free-form text.
196 |
197 | ```ruby
198 | field :short_text do
199 | ask "What do you think of me?"
200 | max_characters 3
201 | end
202 | ```
203 |
204 | ### Long text
205 |
206 | A question where the answer is free-form, like `short_text`, but can be *much* longer.
207 |
208 | ```ruby
209 | field :long_text do
210 | ask "What do you *really* think of me?"
211 | max_characters 700
212 | end
213 | ```
214 |
215 | ### Multiple choice
216 |
217 | A question that allows the user to choose from a range answers.
218 |
219 | ```ruby
220 | field :multiple_choice do
221 | ask "Why not both?"
222 |
223 | choice "Yes"
224 | choice "No"
225 |
226 | allow_multiple_selections
227 | randomize
228 | end
229 | ```
230 |
231 | ### Picture choice
232 |
233 | Similar to `multiple_choice`, only you can add a picture to each answer too. This will handle the complications around image uploading for you if you're dealing with local files and pass along your AWS credentials. Otherwise, it will work with whatever system you already have in place – just give it a URL instead of a file path.
234 |
235 |
236 | ```ruby
237 | field :picture_choice do
238 | ask "Which of these is a spoon?"
239 |
240 | # `image` can be a `String`, a `URL`, a `Pathname`, or a `File`
241 | choice "Knife", picture: "http://iseeyouveplayedknifeyspooneybefore.com/spoon.jpg"
242 | choice "Spoon", picture: Rails.root.join("app/assets/images/knife.jpg")
243 | choice "Spork", picture: "/var/www/images/spork.png"
244 |
245 | allow_multiple_selections
246 | randomize
247 | end
248 | ```
249 |
250 | ### Dropdown
251 |
252 | Similar again to `multiple_choice`, when you have too many options to show at once.
253 |
254 | ```ruby
255 | field :dropdown do
256 | ask "Which is the odd one out?"
257 |
258 |
259 | (1..100).each do |number|
260 | choice (number != 70 ? number : "seventy")
261 | end
262 |
263 | in_alphabetical_order
264 | end
265 | ```
266 |
267 | ### Yes/No
268 |
269 | A question that demands the user to commit to their own certainty.
270 |
271 | ```ruby
272 | field :yes_no do
273 | ask "Will you marry me?"
274 | required
275 | end
276 | ```
277 |
278 | ### Number
279 |
280 | A `short_text` style question that only accepts numerical input. It can be limited to a range.
281 |
282 | ```ruby
283 | field :number do
284 | ask "How many fingers am I holding up?"
285 |
286 | min 0
287 | max 4
288 |
289 | # alternatively
290 | between 0..4
291 | end
292 | ```
293 |
294 | ### Rating
295 |
296 | A question that prompts the user to quantify their opinion of something.
297 |
298 | ```ruby
299 | field :rating do
300 | ask "How much did you enjoy Jonny Wiseau's seminal hit, The Room?"
301 |
302 | steps 10
303 | shape "skull"
304 | end
305 |
306 | ```
307 |
308 | ### Opinion Scale
309 |
310 | A refined form of `rating` more appropriate for "bad / neutral / good" style questions.
311 |
312 | ```ruby
313 | field :opinion_scale do
314 | ask "How would you rate our service?"
315 |
316 | steps 11
317 |
318 | left_side "Terrible"
319 | middle "Average"
320 | right_side "Amazeballs"
321 |
322 | starts_from_one
323 | end
324 | ```
325 |
326 | ### Email
327 |
328 | A question type painstakingly created to request a valid email address.
329 |
330 | ```ruby
331 | field :email do
332 | ask "Can I have your email please?"
333 | description "So you can be my best pen-pal buddy forever."
334 | end
335 | ```
336 |
337 | ### Website
338 |
339 | Ask the user to enter a valid URL.
340 |
341 | ```ruby
342 | field :website do
343 | ask "Show me a funny GIF"
344 | end
345 | ```
346 |
347 | ### Legal
348 |
349 | 
350 |
351 | Like the `yes_no` field, but primarily intended for accepting terms and conditions. Stuff like that.
352 |
353 | ```ruby
354 | field :legal
355 | ask "Do you accept my lofty demands?"
356 | required
357 | end
358 | ```
359 |
360 | ## Custom Designs
361 |
362 | You can customise the appearance of your Typeform by adding a design. While you don't have as much control as you would through the builder on Typeform.com, you are still able to play with colours and fonts. The documentation contains a list of [possible font selections](http://docs.typeform.io/docs/designs).
363 |
364 | ```ruby
365 | design do
366 | question_color "#FF0099"
367 | button_color "#ABCDEF"
368 | answer_color "#4AF6E9"
369 | background_color "#000000"
370 |
371 | font "Vollkorn"
372 | end
373 | ```
374 |
375 | If you already have a design and would like to re-use it, you can use an ID from the created form.
376 |
377 | ```ruby
378 | design 12345
379 | ```
380 |
381 | ## Logic Jumps
382 |
383 | A logic jump allows you to change the next questions you ask based on the answer of a previous question. For example, you could have a `yes_no` field that shows one question if the answer is 'yes', and a different question if the answer is 'no'. At the time of writing this is the only supported behaviour for logic jumps.
384 |
385 | Check out the [documentation on Logic Jumps](http://docs.typeform.io/docs/logic-jumps) to understand more about how they work.
386 |
387 | In order to set one up, you need to give the relevant fields a reference. In this case, when the user answers 'no' to the first question about their age, it should immediately go to the next question like normal. If they answer 'yes', though, the form should ask them another question to confirm they're not lying about being grown up.
388 |
389 | ```ruby
390 | field :yes_no do
391 | ask "Are you over 18?"
392 | required
393 |
394 | ref :is_over_18
395 | end
396 |
397 | field :statement do
398 | say "You're too young to continue"
399 | end
400 |
401 | field :yes_no do
402 | ask "Are you *sure* you're over 18?"
403 | required
404 |
405 | ref :is_really_over_18?
406 | end
407 | ```
408 |
409 | Notice how the two `yes_no` fields have a reference. These are what we use to define the logic jump:
410 |
411 | ```ruby
412 | jump from: :is_over_18, to: :is_really_over_18?, if: true
413 |
414 | ```
415 |
416 | If you need to change the structure of a Typeform based on your own data and not that supplied in an answer, then continue on to [**Conditional Fields**](#conditional-fields).
417 |
418 | ## Conditional Fields
419 |
420 | Consider a form where you ask for the user's email address:
421 |
422 | ```ruby
423 | class EmailTypeform
424 | include AskAwesomely::DSL
425 |
426 | field :email do
427 | ask -> (user) { "Hey #{user.name}, what is your email address?" }
428 | required
429 | end
430 |
431 | # ... more fields
432 | end
433 | ```
434 |
435 | What if you already have the user's email? It makes no sense to repeatedly ask for it, does it? You can tell *Ask Awesomely* to not include this field if a certain condition is met; in this case the user having an email address already.
436 |
437 | ```ruby
438 | class EmailTypeform
439 | include AskAwesomely::DSL
440 |
441 | field :email do
442 | ask -> (user) { "Hey #{user.name}, what is your email address?" }
443 | required
444 |
445 | skip if: -> (user) { !user.email.nil? }
446 |
447 | # alternatively
448 | skip unless: -> (user) { user.email.nil? }
449 | end
450 |
451 | # ... more fields
452 | end
453 | ```
454 |
455 | Note that this is not a feature of the Typeform I/O API. These conditions are evaluated at **build time** and not when the form is rendered (as with logic jumps), which means that the field won't be included in the final Typeform at all.
456 |
457 | ## Common Customisations
458 |
459 | Every field type allows you customize the following things:
460 |
461 | - the description: a smaller chunk of text to give extra detail to a question
462 | - tags: small strings to help you identify questions
463 | - answer required: prevent form submission until the question is answered
464 |
465 | ```ruby
466 | field :legal do
467 | # ...
468 | description "Don't accept, I dare you."
469 | required
470 | tags "some-kind-of-tag-for-legal", "wtf"
471 | end
472 | ```
473 |
474 | ## Passing Context
475 |
476 | Building a form full of hard-coded data is all well and good, but it doesn't offer much benefit over using a web interface. What if you want to build personalised forms based on, say, an `ActiveRecord` model?
477 |
478 | Lets create the basic form, with a title and a single question:
479 |
480 | ```ruby
481 | class UserTypeform
482 | include AskAwesomely::DSL
483 |
484 | title -> (user) { "#{user.name}'s New Typeform" }
485 |
486 | field :yes_no do
487 | say -> (user) { "Is this your email address? #{user.email}" }
488 | required
489 | end
490 | end
491 | ```
492 |
493 | Notice that we're now using a lambda for the title and question, instead of a hardcoded string. In this case, we're expecting an object that has a `name` and an `email`, so we can inject that data into the form.
494 |
495 | The next step is to build the form with such an object. For example, in Rails:
496 |
497 | ```ruby
498 | rodrigo = User.create(name: "Rodrigo", email: "rodrigo@example.com")
499 |
500 | typeform = UserTypeform.build(rodrigo)
501 | ```
502 |
503 | Or in plain Ruby:
504 |
505 | ```ruby
506 | gabriela = OpenStruct.new(name: "Gabriela", email: "gabriela@example.com")
507 |
508 | typeform = UserTypeform.build(gabriela)
509 | ```
510 |
511 |
512 | ## Rendering the Typeform
513 |
514 | Calling `build` will send your Typeform structure to the API right away, and if everything is hunky-dory you'll get a nice new `Typeform` object to play with.
515 |
516 | ### Getting the URL
517 |
518 | Every Typeform you successfully generate through Typeform I/O will come back with a new public URL. This points to the rendered version of the Typeform and it's what you can send out to your users, or participants, or whomever.
519 |
520 | For example, you might email a bunch of personalised Typeforms in a Rails app like this:
521 |
522 | ```ruby
523 |
524 | User.find_each do |user|
525 | typeform = UserTypeform.build(user)
526 | TypeformMailer.send_to_user(user, typeform.public_url)
527 | end
528 | ```
529 |
530 | ### Embedding
531 |
532 | You can also embed a form straight away if you prefer. `AskAwesomely` generates the correct embed code for you, with the correct URL and Typeform title. The style can be customised with CSS, and you can also tweak some of the output.
533 |
534 | To see what each embedding option looks like, check out the [**Embedding Modes**](http://docs.typeform.io/docs/embedding-introduction) documentation at Typeform I/O. It has pictures and everything.
535 |
536 | Assuming you have built a Typeform as in the other examples, rendering the embed code is simple:
537 |
538 | #### Modal
539 |
540 | Pops up over the page content and fills most of the screen.
541 |
542 | ```ruby
543 | typeform.embed_as(:modal, button_text: "Launch me!")
544 | ```
545 |
546 | #### Widget
547 |
548 | Allows you more control over where the form is embedded and how it appears. Just a box on the page.
549 |
550 | ```ruby
551 | typeform.embed_as(:widget, width: "1024px", height: "768px")
552 | ```
553 |
554 | #### Drawer
555 |
556 | Makes the form slide in from the side of the page, hamburger menu style, and fills at least half of the screen.
557 |
558 | ```ruby
559 | typeform.embed_as(:drawer, button_text: "Launch me!")
560 | ```
561 |
562 | #### Fullscreen
563 |
564 | Becomes the entire page.
565 |
566 | Note that this outputs a **complete** HTML document, CSS and all. If you're working with your own views and layouts this will not embed properly unless inserted into an empty layout. Can work well with Sinatra or other small frameworks if you just want to build a form and display it.
567 |
568 | ```ruby
569 | typeform.embed_as(:fullscreen)
570 | ```
571 |
572 | ## Getting Results
573 |
574 | Typeform I/O uses webhooks to send you the responses to your Typeforms. You can configure the URL by telling it where to send responses to, like this:
575 |
576 | ```ruby
577 | class UserTypeform
578 |
579 | # all the fields ...
580 |
581 | send_responses_to "https://www.my-awesome-website.com/webhooks"
582 | end
583 | ```
584 |
585 | *Ask Awesomely* will warn you if you don't configure this, as Typeform I/O doesn't store the responses for you and they'll be lost in the ether.
586 |
587 | Check the documentation on [results and webhooks](http://docs.typeform.io/docs/results-introduction) to find out more about how this works, what happens when a webhook submission request fails, and how you can deduplicate your submissions.
588 |
589 | ## Development
590 |
591 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
592 |
593 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
594 |
595 |
596 | ## Feedback and Contributions
597 |
598 | Practically every aspect of this gem (save for image uploading) is an extension of the Typeform I/O API. If the API has it, `AskAwesomely` eventually will. Here's what the Typeform I/O peeps say:
599 |
600 | > We are continuously working on and improving Typeform I/O, and we're heavily focused on making the API as simple as possible, but also feature-rich so you can make good use of it. We would be forever grateful if you can leave us feedback. We welcome all questions, and we'd love to talk to you about how you're using Typeform I/O, what you hope for from us in the future, or anything else!
601 |
602 | > We recommend you join our [#Slack group](http://docs.typeform.io/v0.4/page/slack-invite) to chat with us, and with other people using Typeform I/O. You can also ask us questions in our [Q&A section](http://docs.typeform.io/v0.4/discuss), or you can simply send us an email to [support@typeform.io](mailto:support@typeform.io).
603 |
604 | That doesn't preclude contributing to the gem itself by fixing bugs or offering improvements. If you'd like to do that, follow these simple steps:
605 |
606 | 1. Fork it (https://github.com/leemachin/ask_awesomely/fork)
607 | 2. Create your feature branch (`git checkout -b my-new-feature`)
608 | 3. Commit your changes (`git commit -am 'Add some feature'`)
609 | 4. Push to the branch (`git push origin my-new-feature`)
610 | 5. Create a new Pull Request
611 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 |
4 | Rake::TestTask.new do |t|
5 | t.libs << "lib" << "spec"
6 | t.test_files = FileList["spec/**/*_spec.rb"]
7 | t.verbose = true
8 | end
9 |
10 | task default: :test
11 |
12 |
--------------------------------------------------------------------------------
/ask_awesomely.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'ask_awesomely/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "ask_awesomely"
8 | spec.version = AskAwesomely::VERSION
9 | spec.authors = ["Lee Machin"]
10 | spec.email = ["me@mrl.ee"]
11 |
12 | spec.summary = "Create Typeforms on the fly, the Typeform way."
13 | spec.homepage = "https://github.com/leemachin/ask_awesomely"
14 |
15 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16 | spec.bindir = "exe"
17 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18 | spec.require_paths = ["lib"]
19 |
20 | spec.add_dependency "aws-sdk", "~> 2.0"
21 | spec.add_dependency "typhoeus", "~> 0.8"
22 | spec.add_dependency "erubis", "~> 2.7"
23 |
24 | spec.add_development_dependency "bundler", "~> 1.9"
25 | spec.add_development_dependency "rake", "~> 10.0"
26 | spec.add_development_dependency "minitest", "~> 5.8"
27 | spec.add_development_dependency "vcr", "~> 2.9"
28 | end
29 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "ask_awesomely"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require "irb"
14 | IRB.start
15 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 |
5 | bundle install
6 |
7 | # Do any other automated setup that you need to do here
8 |
--------------------------------------------------------------------------------
/lib/ask_awesomely.rb:
--------------------------------------------------------------------------------
1 | require "forwardable"
2 | require "json"
3 | require "uri"
4 | require "erubis"
5 | require "aws-sdk"
6 | require "typhoeus"
7 |
8 | require "ask_awesomely/version"
9 | require "ask_awesomely/json_builder"
10 | require "ask_awesomely/configuration"
11 | require "ask_awesomely/dsl"
12 | require "ask_awesomely/field"
13 | require "ask_awesomely/choice"
14 | require "ask_awesomely/picture"
15 | require "ask_awesomely/s3"
16 | require "ask_awesomely/api_client"
17 | require "ask_awesomely/typeform"
18 | require "ask_awesomely/embeddable"
19 | require "ask_awesomely/logic_jump"
20 | require "ask_awesomely/design"
21 |
22 | module AskAwesomely
23 |
24 | ConfigurationError = Class.new(ArgumentError)
25 | FieldTypeError = Class.new(ArgumentError)
26 | EmbedTypeError = Class.new(ArgumentError)
27 | InvalidUrlError = Class.new(TypeError)
28 | InvalidFontError = Class.new(ArgumentError)
29 |
30 | def self.configuration
31 | @configuration ||= Configuration.new
32 | end
33 |
34 | def self.configure(&block)
35 | block.call(self.configuration)
36 | end
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/api_client.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class ApiClient
3 |
4 | USER_AGENT = "leemachin/ask_awesomely; (v#{AskAwesomely::VERSION})"
5 | BASE_URL = "https://api.typeform.io/v0.4"
6 | ENDPOINTS = {
7 | root: "/",
8 | create_typeform: "/forms",
9 | create_picture: "/images",
10 | create_design: "/designs"
11 | }
12 |
13 | def initialize
14 | Typhoeus::Config.user_agent = USER_AGENT
15 | end
16 |
17 | def get_info
18 | response = request.get(
19 | url_for(:root),
20 | headers: headers
21 | )
22 |
23 | JSON.parse(response.body)
24 | end
25 |
26 | def submit_typeform(typeform)
27 | response = request.post(
28 | url_for(:create_typeform),
29 | headers: headers,
30 | body: typeform.to_json
31 | )
32 |
33 | typeform.tap do |tf|
34 | body = JSON.parse(response.body)
35 | tf.update_with_api_response(body)
36 | end
37 | end
38 |
39 | def submit_picture(picture)
40 | response = request.post(
41 | url_for(:create_picture),
42 | headers: headers,
43 | body: picture.to_json
44 | )
45 |
46 | JSON.parse(response.body)
47 | end
48 |
49 | def submit_design(design)
50 | response = request.post(
51 | url_for(:create_design),
52 | headers: headers,
53 | body: design.to_json
54 | )
55 |
56 | JSON.parse(response.body)
57 | end
58 |
59 | private
60 |
61 | def request
62 | Typhoeus::Request
63 | end
64 |
65 | def url_for(endpoint)
66 | BASE_URL + ENDPOINTS[endpoint]
67 | end
68 |
69 | def headers(others = {})
70 | others.merge({
71 | "X-API-TOKEN" => AskAwesomely.configuration.typeform_api_key,
72 | "Accept" => "application/json",
73 | "Content-Type" => "application/json"
74 | })
75 | end
76 |
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/choice.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Choice
3 |
4 | include JsonBuilder
5 |
6 | attr_reader :state
7 |
8 | def initialize(label:, picture: nil)
9 | @state = OpenStruct.new(
10 | label: label,
11 | )
12 |
13 | if picture
14 | @state.image_id = proc { Picture.new(picture).typeform_id }
15 | end
16 | end
17 |
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/configuration.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Configuration
3 |
4 | DEFAULT_AWS_BUCKET = "typeform_io_images"
5 | DEFAULT_AWS_REGION = "us-east-1"
6 |
7 | attr_accessor :typeform_api_key,
8 | :aws_access_key_id,
9 | :aws_access_key_secret,
10 | :aws_region,
11 | :aws_bucket,
12 | :logger
13 |
14 | def typeform_api_key
15 | unless @typeform_api_key
16 | raise ConfigurationError, "Typeform I/O API Key must be set before use, get one from ."
17 | end
18 |
19 | @typeform_api_key
20 | end
21 |
22 | def aws_region
23 | @aws_region || DEFAULT_AWS_REGION
24 | end
25 |
26 | def aws_bucket
27 | @aws_bucket || DEFAULT_AWS_BUCKET
28 | end
29 |
30 | def logger
31 | @logger ||= Logger.new(STDOUT).tap do |logger|
32 | logger.level = Logger::WARN
33 | end
34 | end
35 |
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/design.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Design
3 |
4 | VALID_FONTS = [
5 | "Acme",
6 | "Arial",
7 | "Arvo",
8 | "Bangers",
9 | "Cabin",
10 | "Cabin Condensed",
11 | "Courier",
12 | "Crete Round",
13 | "Dancing Script",
14 | "Exo",
15 | "Georgia",
16 | "Handlee",
17 | "Karla",
18 | "Lato",
19 | "Lobster",
20 | "Lora",
21 | "McLaren",
22 | "Monsterrat",
23 | "Nixie",
24 | "Old Standard TT",
25 | "Open Sans",
26 | "Oswald",
27 | "Playfair",
28 | "Quicksand",
29 | "Raleway",
30 | "Signika",
31 | "Sniglet",
32 | "Source Sans Pro",
33 | "Vollkorn"
34 | ]
35 |
36 | def initialize(&block)
37 | @state = OpenStruct.new(colors: {})
38 | instance_eval(&block)
39 | end
40 |
41 | def id
42 | @id ||= upload_to_typeform["id"]
43 | end
44 |
45 | def background_color(color)
46 | @state.colors[:background] = color
47 | end
48 |
49 | def question_color(color)
50 | @state.colors[:question] = color
51 | end
52 |
53 | def answer_color(color)
54 | @state.colors[:answer] = color
55 | end
56 |
57 | def button_color(color)
58 | @state.colors[:button] = color
59 | end
60 |
61 | def font(font)
62 | unless VALID_FONTS.include?(font)
63 | raise AskAwesomely::InvalidFontError, "#{font} is not supported; you must use one of the following fonts: #{VALID_FONTS.join(', ')}"
64 | end
65 |
66 | @state.font = font
67 | end
68 |
69 | def to_json(*)
70 | {
71 | colors: @state.colors,
72 | font: @state.font
73 | }.to_json
74 | end
75 |
76 | private
77 |
78 | def upload_to_typeform
79 | ApiClient.new.submit_design(self)
80 | end
81 |
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/dsl.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | module DSL
3 |
4 | def self.included(recv)
5 | recv.extend(ClassMethods)
6 | recv.include(JsonBuilder)
7 | end
8 |
9 | module ClassMethods
10 | def _api_client
11 | @api_client = ApiClient.new
12 | end
13 |
14 | def _state
15 | @state ||= OpenStruct.new(
16 | title: "",
17 | fields: []
18 | )
19 | end
20 |
21 | def build(context = nil)
22 | structure = new(context)
23 | typeform = Typeform.new(structure)
24 | _api_client.submit_typeform(typeform)
25 | end
26 |
27 | def title(title)
28 | _state.title = title
29 | end
30 |
31 | def tags(*tags)
32 | _state.tags = tags
33 | end
34 |
35 | def field(type, &block)
36 | _state.fields << Field::Field.of_type(type, &block)
37 | end
38 |
39 | def jump(conditions)
40 | _state.logic_jumps ||= []
41 | _state.logic_jumps << LogicJump.new(conditions)
42 | end
43 |
44 | def design(id = nil, &block)
45 | _state.design_id = id || ->(_) { Design.new(&block).id }
46 | end
47 |
48 | def no_branding
49 | _state.branding = false
50 | end
51 |
52 | def send_responses_to(url)
53 | unless url =~ /\A#{URI::regexp(['http', 'https'])}\z/
54 | raise AskAwesomely::InvalidUrlError, "you must use a valid URL for webhooks, e.g https://example.com/webhook"
55 | end
56 |
57 | _state.webhook_submit_url = url
58 | end
59 | end
60 |
61 | attr_reader :context, :json, :state
62 |
63 | def to_json
64 | warn_if_no_webhook_set
65 | build_json(context).to_json
66 | end
67 |
68 | private
69 |
70 | def initialize(context = nil)
71 | @context = context
72 | @state = self.class._state
73 | end
74 |
75 | def warn_if_no_webhook_set
76 | return if state.webhook_submit_url
77 | AskAwesomely.configuration.logger.warn(<<-STR.gsub(/^\s*/, ''))
78 | Your Typeform has no webhook URL! The responses to this form **will NOT** be saved!
79 | To set one, add `send_responses_to "https://example.com/webhook"` to #{self.class.name}
80 | STR
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/embeddable.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Embeddable
3 |
4 | EMBED_TYPES = %i(
5 | modal
6 | drawer
7 | widget
8 | fullscreen
9 | )
10 |
11 | DEFAULT_OPTIONS = {
12 | width: 800,
13 | height: 600,
14 | button_text: "Launch me!"
15 | }
16 |
17 | def initialize(embed_type)
18 | unless EMBED_TYPES.include?(embed_type)
19 | raise AskAwesomely::EmbedTypeError, "embed type must be one of: #{EMBED_TYPES.join(', ')}"
20 | end
21 |
22 | @embed_type = embed_type
23 | end
24 |
25 | def render(typeform, options = {})
26 | locals = DEFAULT_OPTIONS
27 | .merge(options)
28 | .merge(public_url: typeform.public_url, title: typeform.title)
29 | Erubis::Eruby.new(template).result(locals)
30 | end
31 |
32 | private
33 |
34 | def template
35 | Pathname.new(__dir__).join("embeds", "#{@embed_type}.erb").read
36 | end
37 |
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/embeds/drawer.erb:
--------------------------------------------------------------------------------
1 | <%= button_text -%>
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/embeds/fullscreen.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= title %>
6 |
7 |
8 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/embeds/modal.erb:
--------------------------------------------------------------------------------
1 | <%= button_text -%>
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/embeds/widget.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | module Field
3 | require "ask_awesomely/field/field"
4 | require "ask_awesomely/field/short_text"
5 | require "ask_awesomely/field/long_text"
6 | require "ask_awesomely/field/multiple_choice"
7 | require "ask_awesomely/field/picture_choice"
8 | require "ask_awesomely/field/statement"
9 | require "ask_awesomely/field/number"
10 | require "ask_awesomely/field/dropdown"
11 | require "ask_awesomely/field/yes_no"
12 | require "ask_awesomely/field/rating"
13 | require "ask_awesomely/field/opinion_scale"
14 | require "ask_awesomely/field/email"
15 | require "ask_awesomely/field/website"
16 | require "ask_awesomely/field/legal"
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/dropdown.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::Dropdown < Field::Field
3 |
4 | def choice(label)
5 | @state.choices ||= []
6 | @state.choices << Choice.new(label: label)
7 | end
8 |
9 | def in_alphabetical_order
10 | @state.alphabetical_order = true
11 | end
12 |
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/email.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::Email < Field::Field
3 |
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/field.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::Field
3 |
4 | prepend JsonBuilder
5 |
6 | VALID_FIELD_TYPES = %i(
7 | short_text
8 | long_text
9 | multiple_choice
10 | picture_choice
11 | statement
12 | dropdown
13 | yes_no
14 | number
15 | rating
16 | opinion_scale
17 | email
18 | website
19 | legal
20 | ).freeze
21 |
22 | attr_reader :state
23 |
24 | def self.of_type(type, &block)
25 | field_type = type.to_s.split('_').map(&:capitalize).join
26 | field = AskAwesomely::Field.const_get(field_type)
27 | field.new(type, &block)
28 | rescue NameError => e
29 | raise FieldTypeError, "Field #{type} <#{field}> does not exist, please use one of: #{VALID_FIELD_TYPES.join(", ")}"
30 | end
31 |
32 | # Allow init with properties common to *all* fields
33 | def initialize(type, &block)
34 | @state = OpenStruct.new(type: type)
35 |
36 | self.instance_eval(&block) if block_given?
37 | end
38 |
39 | def ask(question)
40 | @state.question = question
41 | end
42 | alias_method :say, :ask
43 |
44 |
45 | def description(text)
46 | @state.description = text
47 | end
48 |
49 | def required
50 | @state.required = true
51 | end
52 |
53 | def tags(*tags)
54 | @state.tags = tags
55 | end
56 |
57 | def ref(name)
58 | @state.ref = name.to_s
59 | end
60 |
61 | def skip(condition)
62 | if condition[:if]
63 | cond_if = condition[:if]
64 | @state.skip = -> (context) { cond_if.call(context) == true }
65 | end
66 |
67 | if condition[:unless]
68 | cond_unless = condition[:unless]
69 | @state.skip = -> (context) { cond_unless.call(context) != true }
70 | end
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/legal.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::Legal < Field::Field
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/long_text.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::LongText < Field::Field
3 |
4 | def max_characters(num)
5 | @state.max_characters = num
6 | end
7 |
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/multiple_choice.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::MultipleChoice < Field::Field
3 |
4 | def choice(label)
5 | @state.choices ||= []
6 | @state.choices << Choice.new(label: label)
7 | end
8 |
9 | def allow_multiple_selections
10 | @state.allow_multiple_selections = true
11 | end
12 |
13 | def randomize
14 | @state.randomize = true
15 | end
16 |
17 | def can_specify_other
18 | @state.add_other_choice = true
19 | end
20 |
21 | def align_vertically
22 | @state.vertical_alignment = true
23 | end
24 |
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/number.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::Number < Field::Field
3 |
4 | def initialize(*)
5 | super
6 | end
7 |
8 | def min(min)
9 | @state.min_value = min
10 | end
11 |
12 | def max(max)
13 | @state.max_value = max
14 | end
15 |
16 | def between(range)
17 | min(range.begin)
18 | max(range.end)
19 | end
20 |
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/opinion_scale.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::OpinionScale < Field::Field
3 |
4 | POSSIBLE_STEPS = 5..11
5 |
6 | def steps(steps)
7 | unless POSSIBLE_STEPS.cover?(steps)
8 | raise ArgumentError, "number of steps must be between #{POSSIBLE_STEPS.begin} and #{POSSIBLE_STEPS.end}"
9 | end
10 |
11 | @state.steps = steps
12 | end
13 |
14 | def starts_from_one
15 | @state.start_at_one = true
16 | end
17 |
18 | def left_side(label)
19 | @state.labels ||= Hash.new(nil)
20 | @state.labels[:left] = label
21 | end
22 |
23 | def middle(label)
24 | @state.labels ||= Hash.new(nil)
25 | @state.labels[:center] = label
26 | end
27 |
28 | def right_side(label)
29 | @state.labels ||= Hash.new(nil)
30 | @state.labels[:right] = label
31 | end
32 |
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/picture_choice.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::PictureChoice < Field::Field
3 |
4 | def choice(label, picture:)
5 | @state.choices ||= []
6 | @state.choices << Choice.new(label: label, picture: picture)
7 | end
8 |
9 | def allow_multiple_selections
10 | @state.allow_multiple_selections = true
11 | end
12 |
13 | def randomize
14 | @state.randomize = true
15 | end
16 |
17 | def can_specify_other
18 | @state.add_other_choice = true
19 | end
20 |
21 | def show_labels
22 | @state.show_labels = true
23 | end
24 |
25 | def align_vertically
26 | @state.vertical_alignment = true
27 | end
28 |
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/rating.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::Rating < Field::Field
3 |
4 | POSSIBLE_STEPS = 1..10
5 | VALID_SHAPES = [
6 | "star",
7 | "heart",
8 | "user",
9 | "up",
10 | "crown",
11 | "cat",
12 | "dog",
13 | "circle",
14 | "flag",
15 | "droplet",
16 | "tick",
17 | "lightbulb",
18 | "trophy",
19 | "cloud",
20 | "thunderbolt",
21 | "pencil",
22 | "skull"
23 | ]
24 |
25 | def initialize(*)
26 | super
27 | end
28 |
29 | def steps(steps)
30 | unless POSSIBLE_STEPS.cover?(steps)
31 | raise ArgumentError, "number of steps must be between #{POSSIBLE_STEPS.begin} and #{POSSIBLE_STEPS.end}"
32 | end
33 |
34 | @state.steps = steps
35 | end
36 |
37 | def shape(shape)
38 | unless VALID_SHAPES.include?(shape)
39 | raise ArgumentError, "shape must be one of: #{VALID_SHAPES.join(", ")}"
40 | end
41 |
42 | @state.shape = shape
43 | end
44 |
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/short_text.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::ShortText < Field::Field
3 |
4 | def max_characters(num)
5 | @state.max_characters = num
6 | end
7 |
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/statement.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::Statement < Field::Field
3 |
4 | def initialize(*)
5 | super
6 | end
7 |
8 | def no_quotation_marks
9 | @state.hide_marks = true
10 | end
11 |
12 | def button_text(text)
13 | @state.button_text = text
14 | end
15 |
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/website.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::Website < Field::Field
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/field/yes_no.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Field::YesNo < Field::Field
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/json_builder.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | module JsonBuilder
3 |
4 | def build_json(context = nil)
5 | super if defined?(super)
6 |
7 | state.to_h.reduce({}) do |json, (k, v)|
8 |
9 | next if k == :skip && v.call(context) == true
10 |
11 | json[k] = case
12 | when v.respond_to?(:to_ary)
13 | v.map {|f| f.respond_to?(:build_json) ? f.build_json(context) : f }
14 | .compact
15 | when v.respond_to?(:call)
16 | v.call(context)
17 | when v.respond_to?(:build_json)
18 | v.build_json(context)
19 | else
20 | v
21 | end
22 | json
23 | end
24 | end
25 |
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/logic_jump.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class LogicJump
3 |
4 | include JsonBuilder
5 |
6 | attr_reader :state
7 |
8 | def initialize(args)
9 | @state = OpenStruct.new(
10 | to: args[:to],
11 | from: args[:from],
12 | if: args.key?(:if) ? args[:if] : args[:if_answer]
13 | )
14 | end
15 |
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/picture.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Picture
3 |
4 | attr_reader :file_or_url, :public_url, :type, :typeform_id
5 |
6 | DEFAULT_TYPE = :choice
7 |
8 | PICTURE_TYPES = [
9 | :choice
10 | ]
11 |
12 | def initialize(file_or_url, type: DEFAULT_TYPE)
13 | @file_or_url = file_or_url
14 | @type = type
15 | end
16 |
17 | def public_url
18 | @public_url ||= if url?
19 | file_or_url
20 | else
21 | file = upload_to_s3
22 | file.public_url
23 | end
24 | end
25 |
26 | def typeform_id
27 | @id ||= upload_to_typeform["id"]
28 | end
29 |
30 | def to_json(*)
31 | {
32 | url: public_url,
33 | }.to_json
34 | end
35 |
36 | private
37 |
38 | def url?
39 | file_or_url.to_s =~ %r(https?://)
40 | end
41 |
42 | def file?
43 | Pathname.new(file_or_url.to_s).file?
44 | end
45 |
46 | def upload_to_s3
47 | S3.upload(Pathname.new(file_or_url))
48 | end
49 |
50 | def upload_to_typeform
51 | ApiClient.new.submit_picture(self)
52 | end
53 |
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/s3.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class S3
3 |
4 | extend Forwardable
5 |
6 | delegate %i(
7 | aws_access_key_id
8 | aws_access_key_secret
9 | aws_region
10 | aws_bucket
11 | has_aws_credentials?
12 | ) => "AskAwesomely.configuration"
13 |
14 | delegate :public_url => :s3_object
15 |
16 | attr_reader :file, :s3_object
17 |
18 | def initialize(file)
19 | @file = file
20 | end
21 |
22 | def self.upload(file)
23 | new(file).tap(&:upload_file)
24 | end
25 |
26 | def upload_file
27 | ensure_credentials_set!
28 | @s3_object = bucket.object(file.basename.to_s).tap do |obj|
29 | obj.upload_file(file.to_s)
30 | end
31 | end
32 |
33 | private
34 |
35 | def ensure_credentials_set!
36 | unless aws_access_key_id && aws_access_key_secret
37 | raise ConfigurationError, "AWS Access Key ID and AWS Access Key Secret must be set before use"
38 | end
39 | end
40 |
41 | def credentials
42 | Aws::Credentials.new(aws_access_key_id, aws_access_key_secret)
43 | end
44 |
45 | def bucket
46 | Aws::S3::Resource.new(region: aws_region, credentials: credentials)
47 | .bucket(aws_bucket)
48 | end
49 |
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/typeform.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | class Typeform
3 | attr_reader :links, :id, :structure, :fields
4 |
5 | def initialize(structure)
6 | @structure = structure
7 | @fields = []
8 | end
9 |
10 | def title
11 | @structure.class._state.title
12 | end
13 |
14 | def public_url
15 | @public_url ||= links.find do |link|
16 | link['rel'] == 'form_render'
17 | end.fetch('href')
18 | end
19 |
20 | def private_url
21 | @private_url ||= links.find do |link|
22 | link['rel'] == 'self'
23 | end.fetch('href')
24 | end
25 |
26 | def embed_as(type, options = {})
27 | Embeddable.new(type).render(self, options)
28 | end
29 |
30 | def update_with_api_response(response)
31 | @links = response['_links']
32 | @id = response['id']
33 | @fields = response['fields']
34 | end
35 |
36 | def to_json
37 | @structure.to_json
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/ask_awesomely/version.rb:
--------------------------------------------------------------------------------
1 | module AskAwesomely
2 | VERSION = "0.2.9"
3 | end
4 |
--------------------------------------------------------------------------------
/spec/fixtures/basic_form.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "My Example Form",
3 | "fields": [
4 | {
5 | "type": "statement",
6 | "question": "This is a test!"
7 | },
8 | {
9 | "type": "short_text",
10 | "question": "What do you think?",
11 | "required": true,
12 | "tags": [
13 | "opinion"
14 | ]
15 | }
16 | ],
17 | "tags": [
18 | "example",
19 | "first-attempt"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/spec/fixtures/design_form.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Design Typeform",
3 | "fields": [
4 | {
5 | "type": "statement",
6 | "question": "I am just a test"
7 | }
8 | ],
9 | "tags": [
10 | "example",
11 | "with-design"
12 | ],
13 | "design_id": "wUfiDLkhbx"
14 | }
15 |
--------------------------------------------------------------------------------
/spec/fixtures/logic_form.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "My Example Form",
3 | "fields": [
4 | {
5 | "type": "yes_no",
6 | "question": "Shall we continue?",
7 | "ref": "shall_continue"
8 | },
9 | {
10 | "type": "statement",
11 | "question": "Thanks!"
12 | },
13 | {
14 | "type": "statement",
15 | "question": "Wut?",
16 | "ref": "shall_not_continue"
17 | }
18 | ],
19 | "tags": [
20 | "example",
21 | "logic-jumps"
22 | ],
23 | "logic_jumps": [
24 | {
25 | "to": "shall_not_continue",
26 | "from": "shall_continue",
27 | "if": false
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/spec/fixtures/multiple_choice_form.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "My Example Form",
3 | "fields": [
4 | {
5 | "type": "multiple_choice",
6 | "question": "What do you think?",
7 | "choices": [
8 | {
9 | "label": "One"
10 | },
11 | {
12 | "label": "two"
13 | },
14 | {
15 | "label": "Three"
16 | }
17 | ],
18 | "required": true
19 | }
20 | ],
21 | "tags": [
22 | "example",
23 | "multiple-choice"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/spec/fixtures/picture_form.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Picture Typeform",
3 | "fields": [
4 | {
5 | "type": "picture_choice",
6 | "question": "Spoon or fork?",
7 | "choices": [
8 | {
9 | "label": "Spoon",
10 | "image_id": "mDfVVgKRmV"
11 | },
12 | {
13 | "label": "Fork",
14 | "image_id": "RkEWFxSx65"
15 | }
16 | ]
17 | }
18 | ],
19 | "tags": [
20 | "example",
21 | "with-picture"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/spec/fixtures/user_form.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Guillermo's Typeform",
3 | "fields": [
4 | {
5 | "type": "statement",
6 | "question": "Hello, Guillermo!"
7 | },
8 | {
9 | "type": "yes_no",
10 | "question": "Is your email address 'guillermo@example.com'?",
11 | "required": true
12 | }
13 | ],
14 | "tags": [
15 | "example",
16 | "with-context"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/create_design.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: https://api.typeform.io/v0.4/designs
6 | body:
7 | encoding: UTF-8
8 | string: '{"colors":{"question":"#ff3300","background":"#ff0033","answer":"#0033ff","button":"#00ff33"},"font":"Arvo"}'
9 | headers:
10 | User-Agent:
11 | - leemachin/ask_awesomely; (v0.1.6)
12 | X-API-TOKEN:
13 | - ""
14 | Accept:
15 | - application/json
16 | Content-Type:
17 | - application/json
18 | response:
19 | status:
20 | code: 201
21 | message: Created
22 | headers:
23 | Access-Control-Allow-Credentials:
24 | - 'true'
25 | Access-Control-Allow-Headers:
26 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
27 | Access-Control-Allow-Methods:
28 | - GET, POST, PUT, OPTIONS
29 | Access-Control-Allow-Origin:
30 | - "*"
31 | Content-Type:
32 | - application/json
33 | Date:
34 | - Sat, 06 Feb 2016 14:51:58 GMT
35 | Server:
36 | - openresty/1.7.4.1
37 | Content-Length:
38 | - '144'
39 | Connection:
40 | - keep-alive
41 | body:
42 | encoding: UTF-8
43 | string: |
44 | {"id":"5SWktYqkvg","colors":{"answer":"#0033ff","background":"#ff0033","button":"#00ff33","question":"#ff3300"},"font":"Arvo","version":"v0.4"}
45 | http_version: '1.1'
46 | adapter_metadata:
47 | effective_url: https://api.typeform.io/v0.4/designs
48 | recorded_at: Sat, 06 Feb 2016 14:51:58 GMT
49 | recorded_with: VCR 2.9.3
50 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/design_uploads.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: https://api.typeform.io/v0.4/designs
6 | body:
7 | encoding: UTF-8
8 | string: '{"colors":{"background":"#ff0033","answer":"#3300ff","question":"#ff3300","button":"#00ff33"},"font":"Vollkorn"}'
9 | headers:
10 | User-Agent:
11 | - leemachin/ask_awesomely; (v0.1.6)
12 | X-API-TOKEN:
13 | - ""
14 | Accept:
15 | - application/json
16 | Content-Type:
17 | - application/json
18 | response:
19 | status:
20 | code: 201
21 | message: Created
22 | headers:
23 | Access-Control-Allow-Credentials:
24 | - 'true'
25 | Access-Control-Allow-Headers:
26 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
27 | Access-Control-Allow-Methods:
28 | - GET, POST, PUT, OPTIONS
29 | Access-Control-Allow-Origin:
30 | - "*"
31 | Content-Type:
32 | - application/json
33 | Date:
34 | - Sat, 06 Feb 2016 14:36:25 GMT
35 | Server:
36 | - openresty/1.7.4.1
37 | Content-Length:
38 | - '148'
39 | Connection:
40 | - keep-alive
41 | body:
42 | encoding: UTF-8
43 | string: |
44 | {"id":"DZpQHxMYge","colors":{"answer":"#3300ff","background":"#ff0033","button":"#00ff33","question":"#ff3300"},"font":"Vollkorn","version":"v0.4"}
45 | http_version: '1.1'
46 | adapter_metadata:
47 | effective_url: https://api.typeform.io/v0.4/designs
48 | recorded_at: Sat, 06 Feb 2016 14:36:25 GMT
49 | - request:
50 | method: post
51 | uri: https://api.typeform.io/v0.4/forms
52 | body:
53 | encoding: UTF-8
54 | string: '{"title":"Design Typeform","fields":[{"type":"statement","question":"I
55 | am just a test"}],"tags":["example","with-design"],"design_id":"DZpQHxMYge"}'
56 | headers:
57 | User-Agent:
58 | - leemachin/ask_awesomely; (v0.1.6)
59 | X-API-TOKEN:
60 | - ""
61 | Accept:
62 | - application/json
63 | Content-Type:
64 | - application/json
65 | response:
66 | status:
67 | code: 201
68 | message: Created
69 | headers:
70 | Access-Control-Allow-Credentials:
71 | - 'true'
72 | Access-Control-Allow-Headers:
73 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
74 | Access-Control-Allow-Methods:
75 | - GET, POST, PUT, OPTIONS
76 | Access-Control-Allow-Origin:
77 | - "*"
78 | Content-Type:
79 | - application/json
80 | Date:
81 | - Sat, 06 Feb 2016 14:36:25 GMT
82 | Server:
83 | - openresty/1.7.4.1
84 | Content-Length:
85 | - '617'
86 | Connection:
87 | - keep-alive
88 | body:
89 | encoding: UTF-8
90 | string: |
91 | {"id":"AA3UCCZwY4","design":{"id":"DZpQHxMYge","colors":{"answer":"#3300ff","background":"#ff0033","button":"#00ff33","question":"#ff3300"},"font":"Vollkorn","version":"v0.4"},"title":"Design Typeform","fields":[{"id":1857473,"type":"statement","question":"I am just a test"}],"urls":[{"id":"nhiWVbiMkk","form_id":"AA3UCCZwY4","version":"v0.4"}],"_links":[{"rel":"self","href":"https://api.typeform.io/v0.4/forms/AA3UCCZwY4"},{"rel":"form_render","href":"https://forms.typeform.io/to/nhiWVbiMkk"},{"rel":"url","href":"https://api.typeform.io/v0.4/urls/nhiWVbiMkk"}],"tags":["example","with-design"],"version":"v0.4"}
92 | http_version: '1.1'
93 | adapter_metadata:
94 | effective_url: https://api.typeform.io/v0.4/forms
95 | recorded_at: Sat, 06 Feb 2016 14:36:26 GMT
96 | - request:
97 | method: post
98 | uri: https://api.typeform.io/v0.4/designs
99 | body:
100 | encoding: UTF-8
101 | string: '{"colors":{"background":"#ff0033","answer":"#3300ff","question":"#ff3300","button":"#00ff33"},"font":"Vollkorn"}'
102 | headers:
103 | User-Agent:
104 | - leemachin/ask_awesomely; (v0.1.6)
105 | X-API-TOKEN:
106 | - ""
107 | Accept:
108 | - application/json
109 | Content-Type:
110 | - application/json
111 | response:
112 | status:
113 | code: 201
114 | message: Created
115 | headers:
116 | Access-Control-Allow-Credentials:
117 | - 'true'
118 | Access-Control-Allow-Headers:
119 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
120 | Access-Control-Allow-Methods:
121 | - GET, POST, PUT, OPTIONS
122 | Access-Control-Allow-Origin:
123 | - "*"
124 | Content-Type:
125 | - application/json
126 | Date:
127 | - Sat, 06 Feb 2016 14:36:26 GMT
128 | Server:
129 | - openresty/1.7.4.1
130 | Content-Length:
131 | - '148'
132 | Connection:
133 | - keep-alive
134 | body:
135 | encoding: UTF-8
136 | string: |
137 | {"id":"wUfiDLkhbx","colors":{"answer":"#3300ff","background":"#ff0033","button":"#00ff33","question":"#ff3300"},"font":"Vollkorn","version":"v0.4"}
138 | http_version: '1.1'
139 | adapter_metadata:
140 | effective_url: https://api.typeform.io/v0.4/designs
141 | recorded_at: Sat, 06 Feb 2016 14:36:26 GMT
142 | recorded_with: VCR 2.9.3
143 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/get_api_info.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: get
5 | uri: https://api.typeform.io/v0.4/
6 | body:
7 | encoding: US-ASCII
8 | string: ''
9 | headers:
10 | User-Agent:
11 | - leemachin/ask_awesomely; (v0.1.1)
12 | X-API-TOKEN:
13 | - ""
14 | Accept:
15 | - application/json
16 | Content-Type:
17 | - application/json
18 | response:
19 | status:
20 | code: 200
21 | message: OK
22 | headers:
23 | Access-Control-Allow-Credentials:
24 | - 'true'
25 | Access-Control-Allow-Headers:
26 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
27 | Access-Control-Allow-Methods:
28 | - GET, POST, OPTIONS
29 | Access-Control-Allow-Origin:
30 | - "*"
31 | Content-Type:
32 | - application/json
33 | Date:
34 | - Sat, 26 Sep 2015 20:04:00 GMT
35 | Server:
36 | - openresty/1.7.4.1
37 | Content-Length:
38 | - '231'
39 | Connection:
40 | - keep-alive
41 | body:
42 | encoding: UTF-8
43 | string: |
44 | {"description":"Build API for creating forms awesomely","documentation":"https://docs.typeform.io/","name":"Typeform I/O Build API","support":"support@typeform.io","time":"2015-09-26 20:04:00.550727979 +0000 UTC","version":"v0.4"}
45 | http_version: '1.1'
46 | adapter_metadata:
47 | effective_url: https://api.typeform.io/v0.4/
48 | recorded_at: Sat, 26 Sep 2015 20:04:00 GMT
49 | recorded_with: VCR 2.9.3
50 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/logic_jumps.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: https://api.typeform.io/v0.4/forms
6 | body:
7 | encoding: UTF-8
8 | string: '{"title":"My Example Form","fields":[{"type":"yes_no","question":"Shall
9 | we continue?","ref":"shall_continue?"},{"type":"statement","question":"Thanks!"},{"type":"statement","question":"Wut?","ref":"shall_not_continue"}],"tags":["example","logic-jumps"],"jumps":[{"to":"shall_not_continue","from":"shall_continue?","if_answer":false}]}'
10 | headers:
11 | User-Agent:
12 | - leemachin/ask_awesomely; (v0.2.5)
13 | X-API-TOKEN:
14 | - ""
15 | Accept:
16 | - application/json
17 | Content-Type:
18 | - application/json
19 | response:
20 | status:
21 | code: 400
22 | message: Bad Request
23 | headers:
24 | Content-Type:
25 | - application/json
26 | Date:
27 | - Thu, 14 Apr 2016 17:36:22 GMT
28 | Server:
29 | - openresty/1.7.4.1
30 | X-Content-Type-Options:
31 | - nosniff
32 | Content-Length:
33 | - '102'
34 | Connection:
35 | - keep-alive
36 | body:
37 | encoding: UTF-8
38 | string: '{"error":"validation_error","field":"jumps","description":"Additional
39 | property jumps is not allowed"}
40 |
41 | '
42 | http_version: '1.1'
43 | adapter_metadata:
44 | effective_url: https://api.typeform.io/v0.4/forms
45 | recorded_at: Thu, 14 Apr 2016 17:36:22 GMT
46 | recorded_with: VCR 2.9.3
47 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/picture_uploads.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: https://api.typeform.io/v0.4/images
6 | body:
7 | encoding: UTF-8
8 | string: '{"url":"https://upload.wikimedia.org/wikipedia/commons/9/92/Soup_Spoon.jpg"}'
9 | headers:
10 | User-Agent:
11 | - leemachin/ask_awesomely; (v0.1.5)
12 | X-API-TOKEN:
13 | - ""
14 | Accept:
15 | - application/json
16 | Content-Type:
17 | - application/json
18 | response:
19 | status:
20 | code: 201
21 | message: Created
22 | headers:
23 | Access-Control-Allow-Credentials:
24 | - 'true'
25 | Access-Control-Allow-Headers:
26 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
27 | Access-Control-Allow-Methods:
28 | - GET, POST, OPTIONS
29 | Access-Control-Allow-Origin:
30 | - "*"
31 | Content-Type:
32 | - application/json
33 | Date:
34 | - Sun, 18 Oct 2015 13:11:55 GMT
35 | Server:
36 | - openresty/1.7.4.1
37 | Content-Length:
38 | - '197'
39 | Connection:
40 | - keep-alive
41 | body:
42 | encoding: UTF-8
43 | string: |
44 | {"id":"cpH8GWHLmU","type":"choice","url":"https://upload.wikimedia.org/wikipedia/commons/9/92/Soup_Spoon.jpg","filename":"choice-FVrwwSGjywKUhNB5kh8RpgSfZqSMKfvH-default","height":230,"width":230}
45 | http_version: '1.1'
46 | adapter_metadata:
47 | effective_url: https://api.typeform.io/v0.4/images
48 | recorded_at: Sun, 18 Oct 2015 13:11:55 GMT
49 | - request:
50 | method: post
51 | uri: https://api.typeform.io/v0.4/images
52 | body:
53 | encoding: UTF-8
54 | string: '{"url":"https://upload.wikimedia.org/wikipedia/commons/7/7c/Assorted_forks.jpg"}'
55 | headers:
56 | User-Agent:
57 | - leemachin/ask_awesomely; (v0.1.5)
58 | X-API-TOKEN:
59 | - ""
60 | Accept:
61 | - application/json
62 | Content-Type:
63 | - application/json
64 | response:
65 | status:
66 | code: 201
67 | message: Created
68 | headers:
69 | Access-Control-Allow-Credentials:
70 | - 'true'
71 | Access-Control-Allow-Headers:
72 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
73 | Access-Control-Allow-Methods:
74 | - GET, POST, OPTIONS
75 | Access-Control-Allow-Origin:
76 | - "*"
77 | Content-Type:
78 | - application/json
79 | Date:
80 | - Sun, 18 Oct 2015 13:11:56 GMT
81 | Server:
82 | - openresty/1.7.4.1
83 | Content-Length:
84 | - '201'
85 | Connection:
86 | - keep-alive
87 | body:
88 | encoding: UTF-8
89 | string: |
90 | {"id":"Cm7PEGWsJ9","type":"choice","url":"https://upload.wikimedia.org/wikipedia/commons/7/7c/Assorted_forks.jpg","filename":"choice-x2ZZVsBLgXdNqeYyBf8hS2KWmX2L6ANL-default","height":230,"width":230}
91 | http_version: '1.1'
92 | adapter_metadata:
93 | effective_url: https://api.typeform.io/v0.4/images
94 | recorded_at: Sun, 18 Oct 2015 13:11:57 GMT
95 | - request:
96 | method: post
97 | uri: https://api.typeform.io/v0.4/forms
98 | body:
99 | encoding: UTF-8
100 | string: '{"title":"Picture Typeform","fields":[{"type":"picture_choice","question":"Spoon
101 | or fork?","choices":[{"label":"Spoon","image_id":"cpH8GWHLmU"},{"label":"Fork","image_id":"Cm7PEGWsJ9"}]}],"tags":["example","with-picture"]}'
102 | headers:
103 | User-Agent:
104 | - leemachin/ask_awesomely; (v0.1.5)
105 | X-API-TOKEN:
106 | - ""
107 | Accept:
108 | - application/json
109 | Content-Type:
110 | - application/json
111 | response:
112 | status:
113 | code: 201
114 | message: Created
115 | headers:
116 | Access-Control-Allow-Credentials:
117 | - 'true'
118 | Access-Control-Allow-Headers:
119 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
120 | Access-Control-Allow-Methods:
121 | - GET, POST, OPTIONS
122 | Access-Control-Allow-Origin:
123 | - "*"
124 | Content-Type:
125 | - application/json
126 | Date:
127 | - Sun, 18 Oct 2015 13:11:58 GMT
128 | Server:
129 | - openresty/1.7.4.1
130 | Content-Length:
131 | - '405'
132 | Connection:
133 | - keep-alive
134 | body:
135 | encoding: UTF-8
136 | string: |
137 | {"id":"cg4zpujD3A","title":"Picture Typeform","fields":[{"id":348456,"type":"picture_choice","question":"Spoon or fork?","choices":[{"label":"Spoon","image_id":"cpH8GWHLmU"},{"label":"Fork","image_id":"Cm7PEGWsJ9"}]}],"_links":[{"rel":"self","href":"https://api.typeform.io/v0.4/forms/cg4zpujD3A"},{"rel":"form_render","href":"https://forms.typeform.io/to/cg4zpujD3A"}],"tags":["example","with-picture"]}
138 | http_version: '1.1'
139 | adapter_metadata:
140 | effective_url: https://api.typeform.io/v0.4/forms
141 | recorded_at: Sun, 18 Oct 2015 13:11:58 GMT
142 | - request:
143 | method: post
144 | uri: https://api.typeform.io/v0.4/images
145 | body:
146 | encoding: UTF-8
147 | string: '{"url":"https://upload.wikimedia.org/wikipedia/commons/9/92/Soup_Spoon.jpg"}'
148 | headers:
149 | User-Agent:
150 | - leemachin/ask_awesomely; (v0.1.5)
151 | X-API-TOKEN:
152 | - ""
153 | Accept:
154 | - application/json
155 | Content-Type:
156 | - application/json
157 | response:
158 | status:
159 | code: 201
160 | message: Created
161 | headers:
162 | Access-Control-Allow-Credentials:
163 | - 'true'
164 | Access-Control-Allow-Headers:
165 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
166 | Access-Control-Allow-Methods:
167 | - GET, POST, OPTIONS
168 | Access-Control-Allow-Origin:
169 | - "*"
170 | Content-Type:
171 | - application/json
172 | Date:
173 | - Sun, 18 Oct 2015 13:11:58 GMT
174 | Server:
175 | - openresty/1.7.4.1
176 | Content-Length:
177 | - '197'
178 | Connection:
179 | - keep-alive
180 | body:
181 | encoding: UTF-8
182 | string: |
183 | {"id":"mDfVVgKRmV","type":"choice","url":"https://upload.wikimedia.org/wikipedia/commons/9/92/Soup_Spoon.jpg","filename":"choice-t4nRkMKBJATysysFiF2ESXUc7KCfxdCt-default","height":230,"width":230}
184 | http_version: '1.1'
185 | adapter_metadata:
186 | effective_url: https://api.typeform.io/v0.4/images
187 | recorded_at: Sun, 18 Oct 2015 13:11:58 GMT
188 | - request:
189 | method: post
190 | uri: https://api.typeform.io/v0.4/images
191 | body:
192 | encoding: UTF-8
193 | string: '{"url":"https://upload.wikimedia.org/wikipedia/commons/7/7c/Assorted_forks.jpg"}'
194 | headers:
195 | User-Agent:
196 | - leemachin/ask_awesomely; (v0.1.5)
197 | X-API-TOKEN:
198 | - ""
199 | Accept:
200 | - application/json
201 | Content-Type:
202 | - application/json
203 | response:
204 | status:
205 | code: 201
206 | message: Created
207 | headers:
208 | Access-Control-Allow-Credentials:
209 | - 'true'
210 | Access-Control-Allow-Headers:
211 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
212 | Access-Control-Allow-Methods:
213 | - GET, POST, OPTIONS
214 | Access-Control-Allow-Origin:
215 | - "*"
216 | Content-Type:
217 | - application/json
218 | Date:
219 | - Sun, 18 Oct 2015 13:11:58 GMT
220 | Server:
221 | - openresty/1.7.4.1
222 | Content-Length:
223 | - '201'
224 | Connection:
225 | - keep-alive
226 | body:
227 | encoding: UTF-8
228 | string: |
229 | {"id":"RkEWFxSx65","type":"choice","url":"https://upload.wikimedia.org/wikipedia/commons/7/7c/Assorted_forks.jpg","filename":"choice-byBWrtP9vs9RiTXZSGinECbVZvcH4Utp-default","height":230,"width":230}
230 | http_version: '1.1'
231 | adapter_metadata:
232 | effective_url: https://api.typeform.io/v0.4/images
233 | recorded_at: Sun, 18 Oct 2015 13:11:58 GMT
234 | recorded_with: VCR 2.9.3
235 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/submit_basic_typeform.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: https://api.typeform.io/v0.4/forms
6 | body:
7 | encoding: UTF-8
8 | string: '{"title":"My Example Form","fields":[{"type":"statement","question":"This
9 | is a test!"},{"type":"short_text","question":"What do you think?","required":true,"tags":["opinion"]}],"tags":["example","first-attempt"]}'
10 | headers:
11 | User-Agent:
12 | - leemachin/ask_awesomely; (v0.1.1)
13 | X-API-TOKEN:
14 | - ""
15 | Accept:
16 | - application/json
17 | Content-Type:
18 | - application/json
19 | response:
20 | status:
21 | code: 201
22 | message: Created
23 | headers:
24 | Access-Control-Allow-Credentials:
25 | - 'true'
26 | Access-Control-Allow-Headers:
27 | - X-API-TOKEN,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
28 | Access-Control-Allow-Methods:
29 | - GET, POST, OPTIONS
30 | Access-Control-Allow-Origin:
31 | - "*"
32 | Content-Type:
33 | - application/json
34 | Date:
35 | - Sat, 26 Sep 2015 20:04:00 GMT
36 | Server:
37 | - openresty/1.7.4.1
38 | Content-Length:
39 | - '407'
40 | Connection:
41 | - keep-alive
42 | body:
43 | encoding: UTF-8
44 | string: |
45 | {"id":"uc5ag9dzpW","title":"My Example Form","fields":[{"id":267430,"type":"statement","question":"This is a test!"},{"id":267431,"type":"short_text","question":"What do you think?","required":true,"tags":["opinion"]}],"_links":[{"rel":"self","href":"https://api.typeform.io/v0.4/forms/uc5ag9dzpW"},{"rel":"form_render","href":"https://forms.typeform.io/to/uc5ag9dzpW"}],"tags":["example","first-attempt"]}
46 | http_version: '1.1'
47 | adapter_metadata:
48 | effective_url: https://api.typeform.io/v0.4/forms
49 | recorded_at: Sat, 26 Sep 2015 20:04:00 GMT
50 | recorded_with: VCR 2.9.3
51 |
--------------------------------------------------------------------------------
/spec/lib/ask_awesomely/api_client_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe AskAwesomely::ApiClient, 'interface to Typeform I/O' do
4 | subject { AskAwesomely::ApiClient.new }
5 |
6 | describe 'successful API connection and authentication' do
7 | it 'can retrieve the information about Typeform I/O' do
8 | VCR.use_cassette('get_api_info') do
9 | info = subject.get_info
10 | info.delete('time')
11 |
12 | info.must_equal('name' => 'Typeform I/O Build API',
13 | 'description' => 'Build API for creating forms awesomely',
14 | 'version' => 'v0.4',
15 | 'documentation' => 'https://docs.typeform.io/',
16 | 'support' => 'support@typeform.io')
17 | end
18 | end
19 | end
20 |
21 | describe 'saving a typeform' do
22 | it 'updates the Typeform with the new ID and public/private URLs' do
23 | VCR.use_cassette('submit_basic_typeform') do
24 | typeform = BasicTypeform.build
25 |
26 | typeform.id.must_equal('uc5ag9dzpW')
27 | typeform.title.must_equal('My Example Form')
28 | typeform.public_url.must_equal('https://forms.typeform.io/to/uc5ag9dzpW')
29 | typeform.private_url.must_equal('https://api.typeform.io/v0.4/forms/uc5ag9dzpW')
30 | typeform.fields.must_equal([
31 | {"id" => 267430, "type" => "statement", "question" => "This is a test!"},
32 | {"id" => 267431, "type" => "short_text", "question" => "What do you think?", "required" => true, "tags" => ["opinion"]
33 | }
34 | ])
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/spec/lib/ask_awesomely/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "tempfile"
3 |
4 |
5 | describe AskAwesomely::Configuration, "Typeform and optional AWS Config" do
6 |
7 | subject { AskAwesomely::Configuration.new }
8 |
9 | let(:tmp_file) { Tempfile.new(['test', '.jpg']) }
10 | let(:picture) { AskAwesomely::Picture.new(tmp_file) }
11 | let(:api_key) { "abcdefghi" }
12 | let(:secret) { "secret" }
13 | let(:region) { "outer-mongolia" }
14 | let(:bucket) { "ice-bucket" }
15 |
16 | after do
17 | tmp_file.close
18 | end
19 |
20 | describe "Typeform I/O" do
21 | it "should raise an error when no API key is supplied" do
22 | subject.typeform_api_key = nil
23 |
24 | proc {
25 | subject.typeform_api_key
26 | }.must_raise(AskAwesomely::ConfigurationError)
27 | end
28 |
29 | it "should not raise an error when an API key is supplied" do
30 | subject.typeform_api_key = api_key
31 |
32 | subject.typeform_api_key.must_equal(api_key)
33 | end
34 | end
35 |
36 | describe "AWS" do
37 | it "should allow AWS access keys to be configured" do
38 | subject.aws_access_key_id = api_key
39 | subject.aws_access_key_secret = secret
40 |
41 | subject.aws_access_key_id.must_equal(api_key)
42 | subject.aws_access_key_secret.must_equal(secret)
43 | end
44 |
45 | it "should allow a bucket name and location to be configured" do
46 | subject.aws_region = region
47 | subject.aws_bucket = bucket
48 |
49 | subject.aws_region.must_equal(region)
50 | subject.aws_bucket.must_equal(bucket)
51 | end
52 |
53 | it "should have a default bucket name and location" do
54 | subject.aws_region.must_equal(AskAwesomely::Configuration::DEFAULT_AWS_REGION)
55 | subject.aws_bucket.must_equal(AskAwesomely::Configuration::DEFAULT_AWS_BUCKET)
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/spec/lib/ask_awesomely/design_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe AskAwesomely::Design, "Creating designs" do
4 |
5 | subject { AskAwesomely::Design }
6 |
7 | describe "the design ID" do
8 | it "submits a design and comes back with a design ID" do
9 | VCR.use_cassette("create_design") do
10 | design = subject.new do
11 | question_color "#ff3300"
12 | background_color "#ff0033"
13 | answer_color "#0033ff"
14 | button_color "#00ff33"
15 | font "Arvo"
16 | end
17 |
18 | design.id.must_equal("5SWktYqkvg")
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/lib/ask_awesomely/dsl_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe AskAwesomely::DSL, "The Typeform builder DSL" do
4 |
5 | before do
6 | VCR.turn_off!
7 | # we don't care about requests in these tests
8 | Typhoeus.stub(AskAwesomely::ApiClient::BASE_URL).and_return(nil)
9 | end
10 |
11 | after do
12 | Typhoeus::Expectation.clear
13 | VCR.turn_on!
14 | end
15 |
16 | describe "building a Typeform with static data" do
17 | subject { BasicTypeform }
18 | let(:json) { fixture("basic_form") }
19 |
20 | it "has a valid JSON representation when built" do
21 | form = subject.build
22 |
23 | generated_json = JSON.pretty_generate(JSON.parse(form.to_json))
24 | generated_json.must_equal(json)
25 | end
26 | end
27 |
28 | describe "building a Typeform and passing in context" do
29 | subject { UserTypeform }
30 |
31 | let(:user) { { name: "Guillermo", email: "guillermo@example.com" } }
32 | let(:json) { fixture("user_form") }
33 |
34 | it "has a valid JSON representation when built" do
35 | form = subject.build(user)
36 | generated_json = JSON.pretty_generate(JSON.parse(form.to_json))
37 | generated_json.must_equal(json)
38 | end
39 | end
40 |
41 | describe "building a Typeform and using logic jumps" do
42 | subject { LogicTypeform }
43 |
44 | let(:json) { fixture("logic_form") }
45 |
46 | it "has a valid representation when built" do
47 | form = subject.build
48 | generated_json = JSON.pretty_generate(JSON.parse(form.to_json))
49 | generated_json.must_equal(json)
50 | end
51 | end
52 |
53 | describe "building a Typeform with multiple choices and context" do
54 | subject { MultipleChoiceTypeform }
55 |
56 | let(:json) { fixture("multiple_choice_form") }
57 |
58 | it "has a valid JSON representation when built" do
59 | form = subject.build("two")
60 | generated_json = JSON.pretty_generate(JSON.parse(form.to_json))
61 | generated_json.must_equal(json)
62 | end
63 | end
64 |
65 | describe "building a Typeform and using pictures" do
66 | subject { PictureTypeform }
67 |
68 | let(:json) { fixture("picture_form") }
69 |
70 | before do
71 | VCR.turn_on!
72 | end
73 |
74 | it "should replace an image file/url with an ID for Typeform" do
75 | VCR.use_cassette("picture_uploads") do
76 | form = subject.build
77 | generated_json = JSON.pretty_generate(JSON.parse(form.to_json))
78 | generated_json.must_equal(json)
79 | end
80 | end
81 | end
82 |
83 | describe "building a Typeform and using designs" do
84 | subject { DesignTypeform }
85 |
86 | let(:json) { fixture("design_form") }
87 |
88 | before do
89 | VCR.turn_on!
90 | end
91 |
92 | it "should create a typeform with a new design ID" do
93 | VCR.use_cassette("design_uploads") do
94 | form = subject.build
95 | generated_json = JSON.pretty_generate(JSON.parse(form.to_json))
96 | generated_json.must_equal(json)
97 | end
98 | end
99 | end
100 |
101 | describe "webhook urls" do
102 | subject { BasicTypeform }
103 | let(:string_logger) { StringIO.new }
104 |
105 | before do
106 | AskAwesomely.configure do |config|
107 | config.logger = Logger.new(string_logger)
108 | end
109 | end
110 |
111 | it "raises an error when the URL isn't valid" do
112 | proc {
113 | subject.send_responses_to("nonsense-string.com")
114 | }.must_raise(AskAwesomely::InvalidUrlError)
115 | end
116 |
117 | it "warns you when building a form without a webhook URL" do
118 | subject.build
119 | string_logger.string.must_match(/Your Typeform has no webhook URL!/)
120 | end
121 | end
122 |
123 | end
124 |
--------------------------------------------------------------------------------
/spec/lib/ask_awesomely/embeddable_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe AskAwesomely::Embeddable, "embed strings for forms" do
4 |
5 | subject { AskAwesomely::Embeddable }
6 |
7 | let(:typeform) { AskAwesomely::Typeform.new(BasicTypeform.new) }
8 |
9 | before do
10 | typeform.instance_variable_set(:@public_url, "http://typeform.io/to/aBcDeF")
11 | end
12 |
13 | describe "invalid type" do
14 | it "should raise an error if the embed type doesn't exist" do
15 | proc {
16 | subject.new(:lol_type)
17 | }.must_raise(AskAwesomely::EmbedTypeError)
18 | end
19 | end
20 |
21 | describe "modal" do
22 | it "should return the modal embed string with the correct URL" do
23 | embed = subject.new(:modal)
24 | embed.render(typeform).must_match(typeform.public_url)
25 | end
26 | end
27 |
28 | describe "fullscreen" do
29 | it "should return the fullscreen embed string with the correct URL" do
30 | embed = subject.new(:fullscreen)
31 | embed.render(typeform).must_match(typeform.public_url)
32 | end
33 | end
34 |
35 | describe "widget" do
36 | it "should return the widget embed string with the correct URL and title" do
37 | embed = subject.new(:widget)
38 | widget = embed.render(typeform)
39 |
40 | widget.must_match(typeform.public_url)
41 | widget.must_match(typeform.title)
42 | end
43 | end
44 |
45 | describe "drawer" do
46 | it "should return the drawer embed string with the correct URL" do
47 | embed = subject.new(:drawer)
48 | embed.render(typeform).must_match(typeform.public_url)
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/spec/lib/ask_awesomely/field/field_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe AskAwesomely::Field::Field, "A generic Field" do
4 |
5 | subject { AskAwesomely::Field::Field }
6 |
7 | describe "the options available to all field types" do
8 |
9 | it "must be able to define the field type" do
10 | field = subject.new(:fake)
11 | field.state.type.must_equal(:fake)
12 | end
13 |
14 | it "must be able to ask a question" do
15 | field = subject.new :fake do
16 | ask "A question"
17 | end
18 |
19 | field.state.question.must_equal("A question")
20 | end
21 |
22 | it "is optional by default" do
23 | field = subject.new(:fake)
24 | field.state.required.must_equal(nil)
25 | end
26 |
27 | it "must be able to be marked as required" do
28 | field = subject.new(:fake) do
29 | required
30 | end
31 |
32 | field.state.required.must_equal(true)
33 | end
34 |
35 | it "must be able to have tags" do
36 | field = subject.new(:fake) do
37 | tags "a", "b", "c"
38 | end
39 |
40 | field.state.tags.must_equal(["a", "b", "c"])
41 | end
42 |
43 | end
44 |
45 | end
46 |
--------------------------------------------------------------------------------
/spec/lib/ask_awesomely/field_spec.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leemeichin/ask_awesomely/20ae57d38c00bf940a1de684bd2fb06654059c89/spec/lib/ask_awesomely/field_spec.rb
--------------------------------------------------------------------------------
/spec/lib/ask_awesomely/picture_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "tempfile"
3 |
4 | describe AskAwesomely::Picture, "Handling local/remote images" do
5 |
6 | subject { AskAwesomely::Picture }
7 |
8 | let(:fake_image) { Tempfile.new(['fake-image', '.jpg']) }
9 | let(:s3_url) {
10 | "https://s3.amazonaws.com/#{AskAwesomely::Configuration::DEFAULT_AWS_BUCKET}/"
11 | }
12 |
13 | before do
14 | AskAwesomely.configure do |config|
15 | config.aws_access_key_id = "abcde"
16 | config.aws_access_key_secret = "abcde"
17 | end
18 | end
19 |
20 | after do
21 | fake_image.close
22 | end
23 |
24 | describe "the picture's public URL" do
25 | describe "for a picture hosted on the internet" do
26 | it "just returns the URL supplied, as it doesn't need to do anything else" do
27 | picture = subject.new("https://imgur.com/abcdef")
28 | picture.public_url.must_equal("https://imgur.com/abcdef")
29 | end
30 | end
31 |
32 | describe "for a picture stored locally" do
33 | it "uploads the image to S3 and returns a new URL" do
34 | picture = subject.new(fake_image)
35 | picture.public_url.must_equal(s3_url + File.basename(fake_image))
36 | end
37 | end
38 | end
39 |
40 | end
41 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2 |
3 | require "ask_awesomely"
4 | require "minitest/autorun"
5 | require "minitest/spec"
6 | require "minitest/pride"
7 | require "vcr"
8 | require "stringio"
9 |
10 | Dir.glob("#{__dir__}/support/**/*.rb", &method(:require))
11 |
12 | Aws.config.update({
13 | stub_responses: true
14 | })
15 |
16 | VCR.configure do |config|
17 | config.cassette_library_dir = "#{__dir__}/fixtures/vcr_cassettes"
18 | config.hook_into :typhoeus
19 | config.filter_sensitive_data('') do |http|
20 | AskAwesomely.configuration.typeform_api_key
21 | end
22 | end
23 |
24 | AskAwesomely.configure do |config|
25 | config.typeform_api_key = ENV.fetch("TYPEFORM_IO_API_KEY", "fake_api_key")
26 | config.logger = Logger.new(StringIO.new)
27 | end
28 |
29 | def fixture(name)
30 | Pathname.new(__dir__).join("fixtures", "#{name}.json").read.chomp
31 | end
32 |
--------------------------------------------------------------------------------
/spec/support/basic_typeform.rb:
--------------------------------------------------------------------------------
1 | class BasicTypeform
2 | include AskAwesomely::DSL
3 |
4 | title "My Example Form"
5 |
6 | tags "example", "first-attempt"
7 |
8 | field :statement do
9 | say "This is a test!"
10 | end
11 |
12 | field :short_text do
13 | ask "What do you think?"
14 | required
15 | tags "opinion"
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/support/design_typeform.rb:
--------------------------------------------------------------------------------
1 | class DesignTypeform
2 | include AskAwesomely::DSL
3 |
4 | title "Design Typeform"
5 |
6 | tags "example", "with-design"
7 |
8 | design do
9 | background_color "#ff0033"
10 | answer_color "#3300ff"
11 | question_color "#ff3300"
12 | button_color "#00ff33"
13 |
14 | font "Vollkorn"
15 | end
16 |
17 | field :statement do
18 | say "I am just a test"
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/spec/support/logic_typeform.rb:
--------------------------------------------------------------------------------
1 | class LogicTypeform
2 | include AskAwesomely::DSL
3 |
4 | title "My Example Form"
5 |
6 | tags "example", "logic-jumps"
7 |
8 | jump from: :shall_continue, to: :shall_not_continue, if: false
9 |
10 | field :yes_no do
11 | ask "Shall we continue?"
12 | ref :shall_continue
13 | end
14 |
15 | field :statement do
16 | say "Thanks!"
17 | end
18 |
19 | field :statement do
20 | say "Wut?"
21 | ref :shall_not_continue
22 | end
23 |
24 | field :statement do
25 | say "I shouldn't be here"
26 |
27 | skip if: proc { true }
28 | end
29 |
30 | field :statement do
31 | say "I also shouldn't be here"
32 |
33 | skip unless: proc { false }
34 | end
35 |
36 | end
37 |
--------------------------------------------------------------------------------
/spec/support/multiple_choice_typeform.rb:
--------------------------------------------------------------------------------
1 | class MultipleChoiceTypeform
2 | include AskAwesomely::DSL
3 |
4 | title "My Example Form"
5 |
6 | tags "example", "multiple-choice"
7 |
8 | field :multiple_choice do
9 | ask "What do you think?"
10 |
11 | choice "One"
12 | choice ->(two) { two }
13 | choice "Three"
14 |
15 | required
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/support/picture_typeform.rb:
--------------------------------------------------------------------------------
1 | class PictureTypeform
2 | include AskAwesomely::DSL
3 |
4 | title "Picture Typeform"
5 |
6 | tags "example", "with-picture"
7 |
8 | field :picture_choice do
9 | ask "Spoon or fork?"
10 |
11 | choice "Spoon", picture: "https://upload.wikimedia.org/wikipedia/commons/9/92/Soup_Spoon.jpg"
12 | choice "Fork", picture: "https://upload.wikimedia.org/wikipedia/commons/7/7c/Assorted_forks.jpg"
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/spec/support/user_typeform.rb:
--------------------------------------------------------------------------------
1 | class UserTypeform
2 | include AskAwesomely::DSL
3 |
4 | title -> (user) { "#{user[:name]}'s Typeform" }
5 |
6 | tags "example", "with-context"
7 |
8 | field :statement do
9 | say -> (user) { "Hello, #{user[:name]}!" }
10 | end
11 |
12 | field :yes_no do
13 | ask -> (user) {"Is your email address '#{user[:email]}'?" }
14 | required
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------