├── .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 | [![Gem Version](https://badge.fury.io/rb/ask_awesomely.svg)](http://badge.fury.io/rb/ask_awesomely) [![Build Status](https://travis-ci.org/leemachin/ask_awesomely.svg?branch=master)](https://travis-ci.org/leemachin/ask_awesomely) 10 | [![Code Climate](https://codeclimate.com/github/leemachin/ask_awesomely/badges/gpa.svg)](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 | ![YAAAAWWWWN](https://31.media.tumblr.com/3a602d9eb5e18d208b86bc18c4ea0735/tumblr_ns3e06dxFO1tyncywo1_250.gif) 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 | --------------------------------------------------------------------------------