polleverywhere 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -0
- data/Gemfile +5 -1
- data/Guardfile +1 -1
- data/README.md +37 -5
- data/Rakefile +14 -0
- data/docs/api.haml +94 -0
- data/lib/polleverywhere.rb +24 -3
- data/lib/polleverywhere/api.rb +28 -0
- data/lib/polleverywhere/configurable.rb +32 -0
- data/lib/polleverywhere/configuration.rb +33 -0
- data/lib/polleverywhere/core_ext.rb +74 -0
- data/lib/polleverywhere/http.rb +20 -0
- data/lib/polleverywhere/http/adapter.rb +62 -0
- data/lib/polleverywhere/http/request_builder.rb +78 -0
- data/lib/polleverywhere/models.rb +166 -0
- data/lib/polleverywhere/serializable.rb +127 -0
- data/lib/polleverywhere/version.rb +1 -1
- data/polleverywhere.gemspec +3 -1
- data/spec/integration/api_spec.rb +72 -0
- data/spec/lib/polleverywhere_spec.rb +99 -2
- data/spec/spec_helper.rb +9 -1
- metadata +39 -16
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.2@polleverywhere_gem
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# A sample Guardfile
|
2
2
|
# More info at https://github.com/guard/guard#readme
|
3
3
|
|
4
|
-
guard 'rspec', :version => 2, :cli => '--colour' do
|
4
|
+
guard 'rspec', :version => 2, :cli => '--colour --debugger' do
|
5
5
|
watch(%r{^spec/.+_spec\.rb})
|
6
6
|
watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
7
|
watch('spec/spec_helper.rb') { "spec" }
|
data/README.md
CHANGED
@@ -1,12 +1,44 @@
|
|
1
1
|
# Poll Everywhere API Gem
|
2
2
|
|
3
|
-
|
3
|
+
The Poll Everywhere gem is the best way to integrate Poll Everywhere with your applications.
|
4
|
+
|
5
|
+
# Getting Started
|
6
|
+
|
7
|
+
Install the Poll Everywhere rubygem:
|
8
|
+
|
9
|
+
sudo gem install polleverywhere
|
10
|
+
|
11
|
+
If you're using bundler, add the following line to your Gemfile:
|
12
|
+
|
13
|
+
gem "polleverywhere"
|
14
|
+
|
15
|
+
# Accessing your polls
|
16
|
+
|
17
|
+
require 'polleverywhere'
|
18
|
+
|
19
|
+
# Specify your username and password here
|
20
|
+
PollEverywhere.config do
|
21
|
+
username "my_username"
|
22
|
+
password "my_password"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Now start playing!
|
26
|
+
polls = PollEverywhere::Poll.all
|
27
|
+
poll = poll.first
|
28
|
+
poll.close
|
29
|
+
poll.title = "Hey there, I like changing titles around"
|
30
|
+
poll.options = %w[uno dos tres]
|
31
|
+
poll.save
|
32
|
+
poll.open
|
33
|
+
|
34
|
+
You can do all sorts of fun stuff with polls!
|
4
35
|
|
5
36
|
# TODO
|
37
|
+
|
6
38
|
A roadmap for our API:
|
7
39
|
|
8
|
-
*
|
9
|
-
*
|
10
|
-
*
|
11
|
-
*
|
40
|
+
* API models and documentation for more pieces of our application
|
41
|
+
* Asyn/Evented HTTP adapter
|
42
|
+
* App layer specs
|
43
|
+
* Protocol/API layer specs
|
12
44
|
* Implement documentation system around protocol/API layer specs so that we can run our specs againts a staging environment and validate that it works.
|
data/Rakefile
CHANGED
@@ -1,2 +1,16 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
|
+
require 'haml'
|
4
|
+
require 'polleverywhere'
|
5
|
+
|
6
|
+
namespace :doc do
|
7
|
+
desc "Generate API documentation"
|
8
|
+
task :api do
|
9
|
+
PollEverywhere.config do
|
10
|
+
username "test"
|
11
|
+
password "test"
|
12
|
+
end
|
13
|
+
|
14
|
+
puts PollEverywhere::API::Documentation.generate
|
15
|
+
end
|
16
|
+
end
|
data/docs/api.haml
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
!!! html
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title Poll Everywhere API
|
5
|
+
%body
|
6
|
+
%header
|
7
|
+
%h1 Poll Everywhere API Documentation
|
8
|
+
%em Disclaimer: this documentation is only intended for an internal preview. Hold off on using this stuff until we announce it on our blog.
|
9
|
+
|
10
|
+
%section
|
11
|
+
%p This API assumes that you are familar with the low-level semantics of HTTP, including the use of tools like cURL, and JSON.
|
12
|
+
|
13
|
+
%h1 Authentication
|
14
|
+
%p HTTP Basic authentication is currently used for logging into Poll Everywhere accounts.
|
15
|
+
|
16
|
+
%h1 We use JSON
|
17
|
+
%p Simple JSON key-value data structures are used throughout the API application to persist and modify data to the web application. Typically resources will include a root key that corresponds with the name of the name of the resource; from example, multiple choice polls all have the root key 'multiple_choice_poll'.
|
18
|
+
|
19
|
+
%h1 Polls
|
20
|
+
%h2 Multiple choice polls
|
21
|
+
%h3 JSON Attributes
|
22
|
+
%dl
|
23
|
+
%dt=PollEverywhere::MultipleChoicePoll.root_key
|
24
|
+
%dd Root key for multiple choice poll data.
|
25
|
+
%dd
|
26
|
+
%dl
|
27
|
+
-PollEverywhere::MultipleChoicePoll.props.each do |_, prop|
|
28
|
+
%dt=prop.name
|
29
|
+
%dd=prop.description
|
30
|
+
-if prop.name == :options
|
31
|
+
%dd
|
32
|
+
%dl
|
33
|
+
-PollEverywhere::MultipleChoicePoll::Option.props.each do |_, prop|
|
34
|
+
%dt=prop.name
|
35
|
+
%dd=prop.description
|
36
|
+
|
37
|
+
%h3 Creating a multiple choice poll
|
38
|
+
-@mcp = PollEverywhere::MultipleChoicePoll.from_hash(:title => 'Hey dude!', :options => %w[red blue green]).save
|
39
|
+
%code=examples @mcp
|
40
|
+
|
41
|
+
%h3 Changing the title of the multiple choice poll
|
42
|
+
-@mcp.title = "I like different titles"
|
43
|
+
-@mcp.save
|
44
|
+
%code=examples @mcp
|
45
|
+
|
46
|
+
%h3 Closing and opening multiple choice polls
|
47
|
+
%p To close a poll, change the state to "closed"
|
48
|
+
-@mcp.stop
|
49
|
+
%code=examples @mcp
|
50
|
+
|
51
|
+
%p To open it back up (and allow responses to come on through) change the state to "opened"
|
52
|
+
-@mcp.start
|
53
|
+
%code=examples @mcp
|
54
|
+
|
55
|
+
%h3 Delete a multiple choice poll
|
56
|
+
%p When you're totally finished with the poll and you want to tidy things up a bit, you can delete polls
|
57
|
+
-@mcp.destroy
|
58
|
+
%code=examples @mcp
|
59
|
+
|
60
|
+
%h2 Free text polls
|
61
|
+
%h3 JSON Attributes
|
62
|
+
%dl
|
63
|
+
%dt=PollEverywhere::FTP.root_key
|
64
|
+
%dd Root key for free text poll data.
|
65
|
+
%dd
|
66
|
+
%dl
|
67
|
+
-PollEverywhere::FTP.props.each do |_, prop|
|
68
|
+
%dt=prop.name
|
69
|
+
%dd=prop.description
|
70
|
+
|
71
|
+
%h3 Create free text polls
|
72
|
+
%p Creating a free text poll is similar to building a multiple choice poll, just without the options attribute.
|
73
|
+
-@ftp = PollEverywhere::FreeTextPoll.from_hash(:title => "What is the meaning of life?").save
|
74
|
+
%code=examples @ftp
|
75
|
+
|
76
|
+
%h3 Modify a free text poll
|
77
|
+
%p Change the title attribute to change the poll
|
78
|
+
-@ftp.state = "opened"
|
79
|
+
-@ftp.save
|
80
|
+
%code=examples @ftp
|
81
|
+
|
82
|
+
%h3 Delete a free text poll
|
83
|
+
-@ftp.destroy
|
84
|
+
%code=examples @ftp
|
85
|
+
%footer
|
86
|
+
%p
|
87
|
+
This documentation was automatically generated by the Poll Everywhere rubygem at
|
88
|
+
=succeed '.' do
|
89
|
+
%a(href="http://github.com/polleverywhere/polleverywhere") http://github.com/polleverywhere/polleverywhere
|
90
|
+
Community support for the API is available on the
|
91
|
+
%a(href="http://groups.google.com/group/polleverywhere-dev") Poll Everywhere Developer mailing list
|
92
|
+
and paid professional support at
|
93
|
+
=succeed '.' do
|
94
|
+
%a(href="http://www.polleverywhere.com/professional-support") Poll Everywhere
|
data/lib/polleverywhere.rb
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
1
|
+
module PollEverywhere
|
2
|
+
autoload :Configuration, 'polleverywhere/configuration'
|
3
|
+
autoload :HTTP, 'polleverywhere/http'
|
4
|
+
autoload :API, 'polleverywhere/api'
|
5
|
+
autoload :CoreExt, 'polleverywhere/core_ext'
|
6
|
+
autoload :Serializable, 'polleverywhere/serializable'
|
7
|
+
autoload :Configurable, 'polleverywhere/configurable'
|
8
|
+
autoload :Models, 'polleverywhere/models'
|
9
|
+
|
10
|
+
# Lets make life easier for developers and provide shortcuts to module and class names.
|
11
|
+
# PollEverywhere::Model::MultipleChoicePoll now becomes PollEverywhere::MCP. Cool eh? Thank me later.
|
12
|
+
MCP = MultipleChoicePoll = Models::MultipleChoicePoll
|
13
|
+
FTP = FreeTextPoll = Models::FreeTextPoll
|
14
|
+
|
15
|
+
def self.config(&block)
|
16
|
+
@config ||= Configuration.new
|
17
|
+
@config.configure(&block) if block
|
18
|
+
@config
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.http
|
22
|
+
HTTP::RequestBuilder.new
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'haml'
|
2
|
+
|
3
|
+
module PollEverywhere
|
4
|
+
module API
|
5
|
+
# Generates API integration documentation from HAML
|
6
|
+
class Documentation
|
7
|
+
attr_reader :template
|
8
|
+
|
9
|
+
def initialize(template)
|
10
|
+
@template = template
|
11
|
+
end
|
12
|
+
|
13
|
+
# Generates API documentation
|
14
|
+
def generate
|
15
|
+
Haml::Engine.new(template, :format => :html5).render(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generate the HAML documentation from the default docs path
|
19
|
+
def self.generate(path=File.expand_path('../../../docs/api.haml', __FILE__))
|
20
|
+
new(File.read(path)).generate
|
21
|
+
end
|
22
|
+
|
23
|
+
def examples(model)
|
24
|
+
model.http.adapter.request.to_curl
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module PollEverywhere # :nodoc
|
2
|
+
# The configurable mixin is a tiny DSL that instance evals words in
|
3
|
+
# a class variable for terse configuration code.
|
4
|
+
module Configurable
|
5
|
+
def self.included(base)
|
6
|
+
base.send :extend, ClassMethods
|
7
|
+
base.send :include, InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
# Pass a block into the instance to configure the attributes on the class
|
12
|
+
def configure(&block)
|
13
|
+
instance_eval(&block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Setup an instance method on the target class that allows the setting
|
19
|
+
# of an attribute both via Class.new.attr = "fun" and Class.new.attr "fun"
|
20
|
+
def configurable(*attrs)
|
21
|
+
attrs.each do |attr|
|
22
|
+
class_eval %{
|
23
|
+
attr_writer :#{attr}
|
24
|
+
|
25
|
+
def #{attr}(val=nil)
|
26
|
+
val ? self.#{attr} = val : @#{attr}
|
27
|
+
end}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module PollEverywhere
|
5
|
+
# Configuration information for the Poll Everywhere gem.
|
6
|
+
class Configuration
|
7
|
+
include Configurable
|
8
|
+
configurable :url, :username, :password, :http_adapter
|
9
|
+
|
10
|
+
# Setup default values for configuration class as instance variables.
|
11
|
+
def initialize
|
12
|
+
self.url = "http://api.polleverywhere.com"
|
13
|
+
self.http_adapter = :sync
|
14
|
+
end
|
15
|
+
|
16
|
+
# Make sure that a set URL is always an URI object.
|
17
|
+
def url=(val)
|
18
|
+
@url = URI.parse(val)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Setup the HTTP adapter that will execute HTTP requests.
|
22
|
+
def http_adapter=(name)
|
23
|
+
@http_adapter = HTTP.adapter(name.to_sym)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Builds an HTTP Basic header for our authentication. Eventually
|
27
|
+
# this form of authorization will be replaced with a token authorization
|
28
|
+
# so that a password is not required.
|
29
|
+
def http_basic_credentials
|
30
|
+
"Basic #{Base64.encode64("#{username}:#{password}")}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module PollEverywhere
|
2
|
+
module CoreExt #:nodoc:
|
3
|
+
|
4
|
+
# A hash with indifferent access and magic predicates.
|
5
|
+
#
|
6
|
+
# hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
|
7
|
+
#
|
8
|
+
# hash[:foo] #=> 'bar'
|
9
|
+
# hash['foo'] #=> 'bar'
|
10
|
+
# hash.foo? #=> true
|
11
|
+
#
|
12
|
+
class HashWithIndifferentAccess < ::Hash #:nodoc:
|
13
|
+
def initialize(hash={})
|
14
|
+
super()
|
15
|
+
hash.each do |key, value|
|
16
|
+
self[convert_key(key)] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](key)
|
21
|
+
super(convert_key(key))
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(key, value)
|
25
|
+
super(convert_key(key), value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(key)
|
29
|
+
super(convert_key(key))
|
30
|
+
end
|
31
|
+
|
32
|
+
def values_at(*indices)
|
33
|
+
indices.collect { |key| self[convert_key(key)] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def merge(other)
|
37
|
+
dup.merge!(other)
|
38
|
+
end
|
39
|
+
|
40
|
+
def merge!(other)
|
41
|
+
other.each do |key, value|
|
42
|
+
self[convert_key(key)] = value
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def convert_key(key)
|
50
|
+
key.is_a?(Symbol) ? key.to_s : key
|
51
|
+
end
|
52
|
+
|
53
|
+
# Magic predicates. For instance:
|
54
|
+
#
|
55
|
+
# options.force? # => !!options['force']
|
56
|
+
# options.shebang # => "/usr/lib/local/ruby"
|
57
|
+
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
|
58
|
+
#
|
59
|
+
def method_missing(method, *args, &block)
|
60
|
+
method = method.to_s
|
61
|
+
if method =~ /^(\w+)\?$/
|
62
|
+
if args.empty?
|
63
|
+
!!self[$1]
|
64
|
+
else
|
65
|
+
self[$1] == args.first
|
66
|
+
end
|
67
|
+
else
|
68
|
+
self[method]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PollEverywhere
|
2
|
+
module HTTP
|
3
|
+
autoload :RequestBuilder, 'polleverywhere/http/request_builder'
|
4
|
+
autoload :Adapter, 'polleverywhere/http/adapter'
|
5
|
+
|
6
|
+
# Shortcut for getting adapater instances
|
7
|
+
def self.adapter(name, &block)
|
8
|
+
Adapter.registry[name].new(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Simple HTTP request/response objects for our adapter and DSL
|
12
|
+
class Request < Struct.new(:method, :url, :headers, :body)
|
13
|
+
def to_curl
|
14
|
+
%(curl -X #{method.to_s.upcase} #{headers.map{|h,v| %(-H "#{h}: #{v}")}.join(" ")} -d "#{body.gsub(/[!"`']/){|m| "\\#{m}" }}" "#{url}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Response = Struct.new(:status, :headers, :body)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
module PollEverywhere
|
4
|
+
module HTTP
|
5
|
+
# HTTP adapters make it possible to deal with syncronous or asyncronous HTTP libraries
|
6
|
+
module Adapter
|
7
|
+
def self.registry
|
8
|
+
@adapters ||= Hash.new{|hash,key| h[k.to_sym] }
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.register(name, klass)
|
12
|
+
registry[name] = klass
|
13
|
+
end
|
14
|
+
|
15
|
+
class Sync
|
16
|
+
attr_accessor :request, :response
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
yield self if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute(request, &block)
|
23
|
+
@request = request
|
24
|
+
# TODO get rid of the dependency for RestClient by sucking it up and using the Ruby HTTP client
|
25
|
+
RestClient::Request.execute(:method => request.method, :url => request.url.to_s, :payload => request.body, :headers => request.headers) do |r,_,_,_|
|
26
|
+
@response = block.call Response.new(r.code, r.headers, r.body)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Doc < Sync
|
32
|
+
def execute(request, &block)
|
33
|
+
requests << request
|
34
|
+
super request, &block
|
35
|
+
end
|
36
|
+
|
37
|
+
def requests
|
38
|
+
@requests ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
def last_requests
|
42
|
+
requests[(@last_request ||= 0)..(@last_request=requests.size)]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Test
|
47
|
+
def execute(request, &block)
|
48
|
+
requests << request
|
49
|
+
block.call(Response.new)
|
50
|
+
end
|
51
|
+
|
52
|
+
def requests
|
53
|
+
@requests ||= []
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
self.register :sync, Sync
|
58
|
+
self.register :doc, Doc
|
59
|
+
self.register :test, Test
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module PollEverywhere
|
5
|
+
module HTTP # :nodoc
|
6
|
+
# DSL for building requests within our application that build a Request object and send them to
|
7
|
+
# an adapter to fulfill the request.
|
8
|
+
class RequestBuilder
|
9
|
+
attr_accessor :method, :body, :headers, :params, :url, :format, :response, :adapter, :base_url
|
10
|
+
|
11
|
+
def initialize(adapter=PollEverywhere.config.http_adapter, base_url=PollEverywhere.config.url)
|
12
|
+
self.adapter = adapter
|
13
|
+
self.base_url = base_url
|
14
|
+
self.headers = {
|
15
|
+
'Content-Type' => 'application/json',
|
16
|
+
'Accept' => 'application/json',
|
17
|
+
'Authorization' => PollEverywhere.config.http_basic_credentials
|
18
|
+
}
|
19
|
+
|
20
|
+
yield self if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
def put(body=nil)
|
24
|
+
self.method, self.body = :put, body
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def post(body=nil)
|
29
|
+
self.method, self.body = :post, body
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(params={})
|
34
|
+
self.method, self.params = :get, params
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete(params={})
|
39
|
+
self.method, self.params = :delete, params
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
# Specify the URL of the request.
|
44
|
+
def to(url)
|
45
|
+
self.url = url
|
46
|
+
self
|
47
|
+
end
|
48
|
+
alias :from :to
|
49
|
+
|
50
|
+
# Specify the mime-type/format of the request.
|
51
|
+
def as(format)
|
52
|
+
self.format = format
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def request
|
57
|
+
Request.new(method, url_for(url), headers, body)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Generate a Request object, send it to an adapter, and have it fullfill the response by
|
61
|
+
# yielding a Response object.
|
62
|
+
def response(&block)
|
63
|
+
adapter.execute request, &block
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
# If a base_url is given, url_for can detect if a given .to is a path or URL, then
|
68
|
+
# setup the approriate URL for the request.
|
69
|
+
def url_for(uri)
|
70
|
+
if uri.to_s !~ /^http/ and base_url
|
71
|
+
"#{base_url}#{uri}"
|
72
|
+
else
|
73
|
+
uri.to_s
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module PollEverywhere # :nodoc
|
2
|
+
module Models # :nodoc
|
3
|
+
|
4
|
+
# Poll is an abstract base classe for multiple choice and free text polls
|
5
|
+
class Poll
|
6
|
+
include Serializable
|
7
|
+
|
8
|
+
prop :id
|
9
|
+
|
10
|
+
prop :updated_at do
|
11
|
+
description %{The date and time the poll was last updated.}
|
12
|
+
end
|
13
|
+
|
14
|
+
prop :title do
|
15
|
+
description %{Name of the poll. The title is visible when viewing charts, tables, and other views.}
|
16
|
+
end
|
17
|
+
|
18
|
+
prop :opened_at do
|
19
|
+
description %{Data and time that the poll was started.}
|
20
|
+
end
|
21
|
+
|
22
|
+
prop :options do
|
23
|
+
description %{The possible choices that people choose for a poll.}
|
24
|
+
end
|
25
|
+
|
26
|
+
prop :permalink do
|
27
|
+
description %{An obscufated ID that's used as a private link for sharing the poll}
|
28
|
+
end
|
29
|
+
|
30
|
+
prop :state do
|
31
|
+
description %{Determines whether or not a poll can recieve responses. If the state is 'closed', the poll won't receive responses from the audience. If the poll is 'opened', the poll can receive responses from the audience. If the state is 'maxed_out', the poll won't receive responses until the account is upgraded to support more poll responses.}
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_accessor :http
|
35
|
+
|
36
|
+
def initialize(http=PollEverywhere.http)
|
37
|
+
self.http = http
|
38
|
+
end
|
39
|
+
|
40
|
+
# Start the poll so that it may receive audience responses
|
41
|
+
def start
|
42
|
+
state = "opened"
|
43
|
+
save
|
44
|
+
end
|
45
|
+
|
46
|
+
# Stop the poll so that it stops receieving responses
|
47
|
+
def stop
|
48
|
+
state = "closed"
|
49
|
+
save
|
50
|
+
end
|
51
|
+
|
52
|
+
def persisted?
|
53
|
+
!!permalink
|
54
|
+
end
|
55
|
+
|
56
|
+
def save
|
57
|
+
if persisted?
|
58
|
+
http.put(to_json).as(:json).to(path).response do |response|
|
59
|
+
from_json response.body
|
60
|
+
end
|
61
|
+
else
|
62
|
+
http.post(to_json).as(:json).to('/multiple_choice_polls').response do |response|
|
63
|
+
from_json response.body
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.get(permalink)
|
69
|
+
from_hash(:permalink => permalink).fetch
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.all
|
73
|
+
PollEverywhere.http.get.from("/my/polls").as(:json).response do |response|
|
74
|
+
::JSON.parse(response.body).map do |hash|
|
75
|
+
case hash.keys.first.to_sym
|
76
|
+
when MCP.root_key
|
77
|
+
MCP.from_hash(hash)
|
78
|
+
when FTP.root_key
|
79
|
+
MCP.from_hash(hash)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def destroy
|
86
|
+
http.delete(path).response do |response|
|
87
|
+
self.id = self.permalink = nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def path
|
92
|
+
"/#{self.class.root_key}s/#{permalink}" if persisted?
|
93
|
+
end
|
94
|
+
|
95
|
+
def possible_states
|
96
|
+
[:opened, :closed, :maxed_out]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class FreeTextPoll < Poll
|
101
|
+
root_key :free_text_poll
|
102
|
+
|
103
|
+
prop :keyword do
|
104
|
+
description "The keyword that's used to submit a response to the poll from participants that use SMS."
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class MultipleChoicePoll < Poll
|
109
|
+
|
110
|
+
root_key :multiple_choice_poll
|
111
|
+
|
112
|
+
class Option
|
113
|
+
include Serializable
|
114
|
+
|
115
|
+
prop :id
|
116
|
+
|
117
|
+
prop :value do
|
118
|
+
description "Text that is displayed in the chart that represents what a participant chooses when they response to a poll."
|
119
|
+
end
|
120
|
+
|
121
|
+
prop :keyword do
|
122
|
+
description "The keyword that's used to submit a response to the poll from participants that use SMS."
|
123
|
+
end
|
124
|
+
|
125
|
+
attr_reader :poll
|
126
|
+
|
127
|
+
def initialize(poll)
|
128
|
+
@poll = poll
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Choices for a multiple choice poll
|
133
|
+
def options
|
134
|
+
@options ||= []
|
135
|
+
end
|
136
|
+
|
137
|
+
# Accept an array of options as strings, hashes, or options objects.
|
138
|
+
def options=(options)
|
139
|
+
@options = options.map do |val|
|
140
|
+
case val
|
141
|
+
when Option
|
142
|
+
val.poll = self
|
143
|
+
val
|
144
|
+
when Hash
|
145
|
+
Option.new(self).from_hash(val)
|
146
|
+
else
|
147
|
+
Option.new(self).from_hash(:value => val.to_s)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def fetch
|
153
|
+
http.get.from(path).as(:json).response do |response|
|
154
|
+
from_json response.body
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Add the serialize options hash to the meix
|
159
|
+
def to_hash
|
160
|
+
hash = super
|
161
|
+
hash[:multiple_choice_poll][:options] = options.map(&:to_hash)
|
162
|
+
hash
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module PollEverywhere
|
4
|
+
module Serializable
|
5
|
+
def self.included(base)
|
6
|
+
base.send :extend, ClassMethods
|
7
|
+
base.send :include, InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
# Property is somewhat of a metaclass that stores validations, name, etc.
|
11
|
+
# for the fields that belond to a serializable model. Since a Property lives
|
12
|
+
# at the class level, we have a Value class that
|
13
|
+
class Property
|
14
|
+
include Configurable
|
15
|
+
|
16
|
+
attr_accessor :name, :validations
|
17
|
+
|
18
|
+
configurable :description
|
19
|
+
|
20
|
+
def initialize(name, configuration={})
|
21
|
+
self.name = name.to_sym
|
22
|
+
# Set attributes on the class from a given hash
|
23
|
+
configuration.each do |attr, args|
|
24
|
+
self.send("#{attr}=", *args) if self.respond_to? "#{attr}="
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def value
|
29
|
+
Value.new(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Contains the value of the property, runs validations, and
|
33
|
+
# tracks property changes
|
34
|
+
class Value
|
35
|
+
attr_reader :property, :original
|
36
|
+
attr_accessor :current
|
37
|
+
|
38
|
+
def initialize(property)
|
39
|
+
@property = property
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
# Set or get the root key of the model
|
46
|
+
def root_key(name=nil)
|
47
|
+
name ? @name = name.to_sym : @name
|
48
|
+
end
|
49
|
+
|
50
|
+
# Define a property at the class level
|
51
|
+
def prop(name, &block)
|
52
|
+
prop = props[name]
|
53
|
+
prop.instance_eval(&block) if block
|
54
|
+
prop
|
55
|
+
end
|
56
|
+
|
57
|
+
# Setup attributes hash and delegate setters/getters to the base class.
|
58
|
+
def props
|
59
|
+
@props ||= Hash.new do |props,name|
|
60
|
+
# Define the methods at the instance level
|
61
|
+
class_eval %{
|
62
|
+
def #{name}=(val)
|
63
|
+
props[:#{name}].current = val
|
64
|
+
end
|
65
|
+
|
66
|
+
def #{name}
|
67
|
+
props[:#{name}].current
|
68
|
+
end}
|
69
|
+
|
70
|
+
# Setup the attribute class
|
71
|
+
props[name] = Property.new(name)
|
72
|
+
|
73
|
+
end.merge superclass_props
|
74
|
+
end
|
75
|
+
|
76
|
+
# Instanciate a class from a hash
|
77
|
+
def from_hash(hash)
|
78
|
+
new.from_hash(hash)
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
# Grab attributes from the superclass, clone them, and then we can modify them locally.
|
83
|
+
def superclass_props
|
84
|
+
(superclass.respond_to?(:props) && superclass.props.clone) || {}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module InstanceMethods
|
89
|
+
def props
|
90
|
+
@props ||= self.class.props.inject CoreExt::HashWithIndifferentAccess.new do |hash, (name, property)|
|
91
|
+
hash[property.name] = property.value
|
92
|
+
hash
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_hash
|
97
|
+
hash = props.inject CoreExt::HashWithIndifferentAccess.new do |hash, (name, value)|
|
98
|
+
hash[name] = self.send(name)
|
99
|
+
hash
|
100
|
+
end
|
101
|
+
# Add a root key if one is specified
|
102
|
+
self.class.root_key ? { self.class.root_key => hash } : hash
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set the attributes from a hash
|
106
|
+
def from_hash(hash)
|
107
|
+
hash = CoreExt::HashWithIndifferentAccess.new(hash)
|
108
|
+
# Pop off the root key if one is specified and given
|
109
|
+
self.class.root_key and hash[self.class.root_key] and hash = hash[self.class.root_key]
|
110
|
+
# Then set the attributes on the klass if they're present
|
111
|
+
hash.each do |name, value|
|
112
|
+
self.send("#{name}=", value) if self.respond_to? name
|
113
|
+
end
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_json
|
118
|
+
JSON.pretty_generate(to_hash)
|
119
|
+
end
|
120
|
+
|
121
|
+
def from_json(json)
|
122
|
+
from_hash JSON.parse(json)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
data/polleverywhere.gemspec
CHANGED
@@ -13,9 +13,11 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = %q{An easy way to integrate Poll Everywhere into your Ruby applications.}
|
14
14
|
|
15
15
|
s.rubyforge_project = "polleverywhere"
|
16
|
+
s.add_dependency "json"
|
17
|
+
s.add_development_dependency "rest-client", "~> 1.6.3"
|
16
18
|
|
17
19
|
s.files = `git ls-files`.split("\n")
|
18
20
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
21
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
22
|
s.require_paths = ["lib"]
|
21
|
-
end
|
23
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "API" do
|
4
|
+
context "polls" do
|
5
|
+
it "should get collection from my poll" do
|
6
|
+
PollEverywhere::Models::Poll.all.should have_at_least(1).items
|
7
|
+
end
|
8
|
+
|
9
|
+
context "multiple choice" do
|
10
|
+
before(:all) do
|
11
|
+
@mcp = PollEverywhere::MultipleChoicePoll.from_hash(:title => 'Hey dude!', :options => %w[red blue green])
|
12
|
+
end
|
13
|
+
|
14
|
+
context "creation" do
|
15
|
+
before(:all) do
|
16
|
+
@mcp.save
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should have id" do
|
20
|
+
@mcp.id.should_not be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have permalink" do
|
24
|
+
@mcp.permalink.should_not be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
context "options" do
|
28
|
+
it "should have ids" do
|
29
|
+
@mcp.options.each do |o|
|
30
|
+
o.id.should_not be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have values" do
|
35
|
+
@mcp.options.each do |o|
|
36
|
+
o.value.should_not be_nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "get" do
|
43
|
+
it "should get poll" do
|
44
|
+
@gotten_mcp = PollEverywhere::MultipleChoicePoll.get(@mcp.permalink)
|
45
|
+
@gotten_mcp.title.should eql(@mcp.title)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "updates" do
|
50
|
+
before(:all) do
|
51
|
+
@mcp.title = "My pita bread is moldy"
|
52
|
+
@mcp.options.first.value = "MOLD SUCKS!"
|
53
|
+
@mcp.save
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should update options" do
|
57
|
+
@mcp.options.first.value.should eql("MOLD SUCKS!")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should update title" do
|
61
|
+
@mcp.title.should eql("My pita bread is moldy")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should destroy" do
|
66
|
+
@mcp.destroy
|
67
|
+
@mcp.id.should be_nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -1,5 +1,102 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "
|
4
|
-
|
3
|
+
describe "Configuration" do
|
4
|
+
before(:all) do
|
5
|
+
@config = PollEverywhere::Configuration.new
|
6
|
+
end
|
7
|
+
|
8
|
+
%w[username password].each do |attr|
|
9
|
+
it "should have '#{attr}' configurable attribute" do
|
10
|
+
@config.send(attr, "test_val")
|
11
|
+
@config.send(attr).should eql("test_val")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "url" do
|
16
|
+
it "should default to 'http://api.polleverywhere.com'" do
|
17
|
+
@config.url.to_s.should eql("http://api.polleverywhere.com")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have URI instance for url configurable attribute" do
|
21
|
+
@config.url = "http://loser.com"
|
22
|
+
@config.url.to_s.should eql("http://loser.com")
|
23
|
+
@config.url.should be_instance_of(URI::HTTP)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have a root configuration class" do
|
28
|
+
PollEverywhere.config.should be_instance_of(PollEverywhere::Configuration)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "documentation" do
|
33
|
+
it "should show curl format" do
|
34
|
+
poll = PollEverywhere::MultipleChoicePoll.new
|
35
|
+
poll.title = 'cool'
|
36
|
+
poll.options = %w[1 2 3]
|
37
|
+
poll.save
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "Multiple Choice Poll" do
|
42
|
+
context "serialize" do
|
43
|
+
before(:all) do
|
44
|
+
@hash = {:title => 'Hey dude!', :options => %w[red blue green]}
|
45
|
+
@poll = PollEverywhere::MultipleChoicePoll.from_hash(@hash)
|
46
|
+
end
|
47
|
+
|
48
|
+
context "serialization" do
|
49
|
+
it "should serialize options" do
|
50
|
+
@hash[:options].each do |value|
|
51
|
+
@poll.to_hash[:multiple_choice_poll][:options].find{|o| o[:value] == value}.should_not be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "deserialization" do
|
57
|
+
it "should have options" do
|
58
|
+
@hash[:options].each do |value|
|
59
|
+
@poll.options.find{|o| o.value == value }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should set title" do
|
64
|
+
@poll.title.should eql(@hash[:title])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "http" do
|
71
|
+
context "request builder" do
|
72
|
+
|
73
|
+
# it "should post" do
|
74
|
+
# PollEverywhere::HTTP::RequestBuilder.new.post('some data').to('/this/url').response do |response|
|
75
|
+
# response.body.should eql('hello my pretty')
|
76
|
+
# response.status.should eql(200)
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
|
80
|
+
# it "should put" do
|
81
|
+
# PollEverywhere::HTTP::RequestBuilder.new.put('some data').to('/this/url').response do |response|
|
82
|
+
# response.body.should eql('hello my pretty')
|
83
|
+
# response.status.should eql(200)
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
|
87
|
+
# it "should delete" do
|
88
|
+
# PollEverywhere::HTTP::RequestBuilder.new.delete.from('/this/url').response do |response|
|
89
|
+
# response.body.should eql('hello my pretty')
|
90
|
+
# response.status.should eql(200)
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
|
94
|
+
# it "should get" do
|
95
|
+
# PollEverywhere::HTTP::RequestBuilder.new.get(:page => 1).from('/this/url').response do |response|
|
96
|
+
# response.body.should eql('hello my pretty')
|
97
|
+
# response.status.should eql(200)
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
|
101
|
+
end
|
5
102
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: polleverywhere
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 29
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 1
|
10
|
-
version: 0.0.1
|
5
|
+
version: 0.0.2
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Brad Gessler, Steel Fu
|
@@ -15,10 +10,31 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-
|
13
|
+
date: 2011-06-24 00:00:00 -07:00
|
19
14
|
default_executable:
|
20
|
-
dependencies:
|
21
|
-
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: json
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rest-client
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 1.6.3
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
22
38
|
description: An easy way to integrate Poll Everywhere into your Ruby applications.
|
23
39
|
email:
|
24
40
|
- geeks@polleverywhere.com
|
@@ -30,13 +46,25 @@ extra_rdoc_files: []
|
|
30
46
|
|
31
47
|
files:
|
32
48
|
- .gitignore
|
49
|
+
- .rvmrc
|
33
50
|
- Gemfile
|
34
51
|
- Guardfile
|
35
52
|
- README.md
|
36
53
|
- Rakefile
|
54
|
+
- docs/api.haml
|
37
55
|
- lib/polleverywhere.rb
|
56
|
+
- lib/polleverywhere/api.rb
|
57
|
+
- lib/polleverywhere/configurable.rb
|
58
|
+
- lib/polleverywhere/configuration.rb
|
59
|
+
- lib/polleverywhere/core_ext.rb
|
60
|
+
- lib/polleverywhere/http.rb
|
61
|
+
- lib/polleverywhere/http/adapter.rb
|
62
|
+
- lib/polleverywhere/http/request_builder.rb
|
63
|
+
- lib/polleverywhere/models.rb
|
64
|
+
- lib/polleverywhere/serializable.rb
|
38
65
|
- lib/polleverywhere/version.rb
|
39
66
|
- polleverywhere.gemspec
|
67
|
+
- spec/integration/api_spec.rb
|
40
68
|
- spec/lib/polleverywhere_spec.rb
|
41
69
|
- spec/spec_helper.rb
|
42
70
|
has_rdoc: true
|
@@ -53,26 +81,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
81
|
requirements:
|
54
82
|
- - ">="
|
55
83
|
- !ruby/object:Gem::Version
|
56
|
-
hash: 3
|
57
|
-
segments:
|
58
|
-
- 0
|
59
84
|
version: "0"
|
60
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
86
|
none: false
|
62
87
|
requirements:
|
63
88
|
- - ">="
|
64
89
|
- !ruby/object:Gem::Version
|
65
|
-
hash: 3
|
66
|
-
segments:
|
67
|
-
- 0
|
68
90
|
version: "0"
|
69
91
|
requirements: []
|
70
92
|
|
71
93
|
rubyforge_project: polleverywhere
|
72
|
-
rubygems_version: 1.
|
94
|
+
rubygems_version: 1.5.2
|
73
95
|
signing_key:
|
74
96
|
specification_version: 3
|
75
97
|
summary: Integration Poll Everywhere into your Ruby applications
|
76
98
|
test_files:
|
99
|
+
- spec/integration/api_spec.rb
|
77
100
|
- spec/lib/polleverywhere_spec.rb
|
78
101
|
- spec/spec_helper.rb
|