totter 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +17 -0
  2. data/.travis.yml +4 -0
  3. data/Contributing.markdown +19 -0
  4. data/Gemfile +22 -0
  5. data/LICENSE +22 -0
  6. data/Rakefile +21 -0
  7. data/Readme.markdown +49 -0
  8. data/lib/totter.rb +26 -0
  9. data/lib/totter/client.rb +152 -0
  10. data/lib/totter/client/choices.rb +74 -0
  11. data/lib/totter/client/decisions.rb +91 -0
  12. data/lib/totter/client/slugs.rb +19 -0
  13. data/lib/totter/client/timelines.rb +16 -0
  14. data/lib/totter/client/users.rb +54 -0
  15. data/lib/totter/client/votes.rb +25 -0
  16. data/lib/totter/error.rb +51 -0
  17. data/lib/totter/version.rb +4 -0
  18. data/test/cassettes/choices/create_choice_upload.yml +85 -0
  19. data/test/cassettes/choices/create_for_image.yml +87 -0
  20. data/test/cassettes/choices/destroy.yml +122 -0
  21. data/test/cassettes/choices/show.yml +42 -0
  22. data/test/cassettes/decisions/analytics.yml +85 -0
  23. data/test/cassettes/decisions/create.yml +44 -0
  24. data/test/cassettes/decisions/destroy.yml +81 -0
  25. data/test/cassettes/decisions/flag.yml +81 -0
  26. data/test/cassettes/decisions/publish.yml +129 -0
  27. data/test/cassettes/decisions/show.yml +84 -0
  28. data/test/cassettes/decisions/unflag.yml +122 -0
  29. data/test/cassettes/slugs/show.yml +75 -0
  30. data/test/cassettes/timelines/global.yml +165 -0
  31. data/test/cassettes/users/following.yml +194 -0
  32. data/test/cassettes/users/me.yml +43 -0
  33. data/test/cassettes/users/user.yml +49 -0
  34. data/test/cassettes/votes/create.yml +96 -0
  35. data/test/support/client_macros.rb +5 -0
  36. data/test/test_helper.rb +24 -0
  37. data/test/totter/client/choices_test.rb +48 -0
  38. data/test/totter/client/decisions_test.rb +77 -0
  39. data/test/totter/client/slugs_test.rb +11 -0
  40. data/test/totter/client/timelines_test.rb +9 -0
  41. data/test/totter/client/users_test.rb +36 -0
  42. data/test/totter/client/votes_test.rb +15 -0
  43. data/test/totter/client_test.rb +31 -0
  44. data/test/totter_test.rb +17 -0
  45. data/totter.gemspec +24 -0
  46. metadata +155 -0
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ bundler_args: --without development
3
+ rvm:
4
+ - 1.9.3
@@ -0,0 +1,19 @@
1
+ ## Submitting a Pull Request
2
+
3
+ 1. [Fork the repository.][fork]
4
+ 2. [Create a topic branch.][branch]
5
+ 3. Add tests for your unimplemented feature or bug fix.
6
+ 4. Run `bundle exec rake`. If your tests pass, return to step 3.
7
+ 5. Implement your feature or bug fix.
8
+ 6. Run `bundle exec rake`. If your tests fail, return to step 5.
9
+ 7. Run `open coverage/index.html`. If your changes are not completely covered
10
+ by your tests, return to step 3.
11
+ 8. Add documentation for your feature or bug fix.
12
+ 9. Run `bundle exec rake doc`. If your changes are not 100% documented, go
13
+ back to step 8.
14
+ 10. Add, commit, and push your changes.
15
+ 11. [Submit a pull request.][pr]
16
+
17
+ [fork]: http://help.github.com/fork-a-repo/
18
+ [branch]: http://learn.github.com/p/branching.html
19
+ [pr]: http://help.github.com/send-pull-requests/
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Gem dependencies
4
+ gemspec
5
+
6
+ gem 'rake', group: [:development, :test]
7
+
8
+ # Development dependencies
9
+ group :development do
10
+ gem 'yard'
11
+ gem 'redcarpet'
12
+ end
13
+
14
+ # Testing dependencies
15
+ group :test do
16
+ gem 'minitest'
17
+ gem 'minitest-wscolor'
18
+ gem 'webmock', require: 'webmock/minitest'
19
+ gem 'vcr'
20
+ gem 'mocha', require: 'mocha/setup'
21
+ gem 'simplecov', require: false
22
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Seesaw Decisions Corporation
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
9
+ task default: :test
10
+
11
+ begin
12
+ require 'yard'
13
+ YARD::Rake::YardocTask.new(:doc) do |task|
14
+ task.files = ['Readme.markdown', 'LICENSE', 'lib/**/*.rb']
15
+ task.options = [
16
+ '--output-dir', 'doc',
17
+ '--markup', 'markdown',
18
+ ]
19
+ end
20
+ rescue LoadError
21
+ end
@@ -0,0 +1,49 @@
1
+ # Totter
2
+
3
+ Totter, as in teeter-totter, let's you work with the Seesaw API in Ruby.
4
+
5
+ All networking is done with Net::HTTP so you don't have to worry about version conflicts with whatever networking library you may be using.
6
+
7
+ Read the [documentation](http://rubydoc.info/github/seesawco/totter-rb/master/frames) online.
8
+
9
+ [![Build Status](https://travis-ci.org/seesawco/totter-rb.png?branch=master)](https://travis-ci.org/seesawco/totter-rb) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/seesawco/totter-rb)
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ``` ruby
16
+ gem 'totter'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ ``` shell
22
+ $ bundle
23
+ ```
24
+
25
+ Or install it yourself as:
26
+
27
+ ``` shell
28
+ $ gem install totter
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ A client takes an optional access token when you initialize it. If you don't provide one, you can still use it to make unauthenticated requests. If you do provide one, it will set the authorization header for all requests.
34
+
35
+ ``` ruby
36
+ > client = Totter::Client.new(access_token: 'your_access_token')
37
+ > current_user = client.me
38
+ > current_user.username
39
+ #=> "soffes"
40
+ > decision = client.decision(5, 3276)
41
+ #=> "Which lamp for the new apartment?"
42
+ > slug = client.slug('d/3I0n0g')
43
+ > slug.decision.user.username
44
+ #=> "soffes"
45
+ ```
46
+
47
+ ## Contributing
48
+
49
+ See the [contributing guide](Contributing.markdown).
@@ -0,0 +1,26 @@
1
+ require 'totter/version'
2
+ require 'totter/client'
3
+ require 'totter/error'
4
+
5
+ # Totter, as in teeter-totter, let's you work with the Seesaw API in Ruby.
6
+ module Totter
7
+ class << self
8
+ # Alias for Totter::Client.new
9
+ #
10
+ # @return [Totter::Client]
11
+ def new(options = {})
12
+ Client.new(options)
13
+ end
14
+
15
+ # Delegate to Totter::Client.new
16
+ def method_missing(method, *args, &block)
17
+ return super unless new.respond_to?(method)
18
+ new.send(method, *args, &block)
19
+ end
20
+
21
+ # Forward respond_to? to Totter::Client.new
22
+ def respond_to?(method, include_private = false)
23
+ new.respond_to?(method, include_private) || super(method, include_private)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,152 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'multi_json'
5
+ require 'hashie'
6
+
7
+ require 'totter/client/users'
8
+ require 'totter/client/decisions'
9
+ require 'totter/client/slugs'
10
+ require 'totter/client/timelines'
11
+ require 'totter/client/choices'
12
+ require 'totter/client/votes'
13
+
14
+ module Totter
15
+ # API client for interacting with the Seesaw API
16
+ class Client
17
+ include Totter::Client::Decisions
18
+ include Totter::Client::Users
19
+ include Totter::Client::Slugs
20
+ include Totter::Client::Timelines
21
+ include Totter::Client::Choices
22
+ include Totter::Client::Votes
23
+
24
+ attr_reader :access_token
25
+ attr_reader :api_scheme
26
+ attr_reader :api_host
27
+ attr_reader :api_version
28
+
29
+ # Initialize a new client.
30
+ #
31
+ # @param options [Hash] optionally specify `:access_token`, `:api_scheme`, `:api_host`, `:api_version`, or `:client_token`
32
+ def initialize(options = {})
33
+ options = { access_token: options } if options.is_a? String
34
+
35
+ @access_token = options[:access_token] if options[:access_token]
36
+ @api_scheme = (options[:api_scheme] or 'https')
37
+ @api_host = (options[:api_host] or 'api.seesaw.co')
38
+ @api_version = (options[:api_version] or 1)
39
+ @client_token = options[:client_token] if options[:client_token]
40
+ end
41
+
42
+ # API base URL.
43
+ #
44
+ # @return [String] API base URL
45
+ def base_url
46
+ "#{@api_scheme}://#{@api_host}/v#{@api_version}/"
47
+ end
48
+
49
+ # Is the client has an access token.
50
+ #
51
+ # @return [Boolean] true if it is using one and false if it is not
52
+ def authenticated?
53
+ @access_token != nil and @access_token.length > 0
54
+ end
55
+
56
+ # Is the client using SSL.
57
+ #
58
+ # @return [Boolean] true if it is using SSL and false if it is not
59
+ def ssl?
60
+ @api_scheme == 'https'
61
+ end
62
+
63
+ private
64
+
65
+ def http
66
+ return @http if @http
67
+
68
+ uri = URI.parse(self.base_url)
69
+ @http = Net::HTTP.new(uri.host, uri.port)
70
+ @http.use_ssl = self.ssl?
71
+ @http
72
+ end
73
+
74
+ def request(method, path, params = nil)
75
+ # Build request
76
+ request = build_request(method, URI.parse("#{self.base_url}#{path}"))
77
+
78
+ # Add headers
79
+ request['Authorization'] = "Bearer #{self.access_token}" if authenticated?
80
+ request['X-Seesaw-Client-Token'] = @client_token if @client_token
81
+ request['Content-Type'] = 'application/json'
82
+
83
+ # Add params as JSON if they exist
84
+ request.body = MultiJson.dump(params) if method == :post and params
85
+
86
+ # Request
87
+ response = http.request(request)
88
+
89
+ # Check for errors
90
+ handle_error(response)
91
+
92
+ # Return the raw response object
93
+ response
94
+ end
95
+
96
+ def build_request(method, uri)
97
+ case method
98
+ when :get
99
+ Net::HTTP::Get.new(uri.request_uri)
100
+ when :post
101
+ Net::HTTP::Post.new(uri.request_uri)
102
+ when :put
103
+ Net::HTTP::Put.new(uri.request_uri)
104
+ when :delete
105
+ Net::HTTP::Delete.new(uri.request_uri)
106
+ end
107
+ end
108
+
109
+ def handle_error(response)
110
+ # Find error or return
111
+ return unless error = Totter::ERROR_MAP[response.code.to_i]
112
+
113
+ # Try to add a useful message
114
+ message = nil
115
+ begin
116
+ message = MultiJson.load(response.body)['error_description']
117
+ rescue MultiJson::DecodeError => e
118
+ end
119
+
120
+ # Raise error
121
+ raise error.new(message)
122
+ end
123
+
124
+ def json_request(*args)
125
+ # Preform request
126
+ response = request(*args)
127
+
128
+ # Parse JSON
129
+ object = MultiJson.load(response.body)
130
+
131
+ # Hash
132
+ return Hashie::Mash.new(object) if object.is_a? Hash
133
+
134
+ # Array
135
+ return object.map { |h| Hashie::Mash.new(h) } if object.is_a? Array
136
+
137
+ # Fallback incase it's not a hash or array
138
+ object
139
+ end
140
+
141
+ def boolean_from_response(*args)
142
+ response = request(*args)
143
+ (200..299).include? response.code.to_i
144
+ end
145
+
146
+ [:get, :post, :put, :delete].each do |method|
147
+ define_method method do |*args|
148
+ json_request(method, *args)
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,74 @@
1
+ module Totter
2
+ class Client
3
+ # Client methods for working with choices
4
+ module Choices
5
+ # Get a single choice
6
+ #
7
+ # @param user_id [Numeric] The choice's decision's user id
8
+ # @param decision_id [Numeric] The choice's decision's id
9
+ # @param choice_id [Numeric] The choice's id
10
+ # @return [Hashie::Mash]
11
+ # @example
12
+ # Seesaw.choice(1, 1, 1)
13
+ def choice(user_id, decision_id, choice_id)
14
+ get "users/#{user_id}/decisions/#{decision_id}/choices/#{choice_id}"
15
+ end
16
+
17
+
18
+ # Create a new choice using a supplied image url
19
+ #
20
+ # @param user_id [Numeric] The choice's decision's user id
21
+ # @param decision_id [Numeric] The choice's decision's id
22
+ # @param options [Hash]
23
+ # @option options [String] :image_url The image url (required)
24
+ # @option options [String] :subject The image label
25
+ # @option options [String] :link_url A link to this image's page
26
+ # @option options [String] :link_title The title of a link to this image's page
27
+ # @return [Hashie::Mash]
28
+ # @example
29
+ # Seesaw.create_choice_for_image(1, 1, image_url: 'http://recess.s3.amazonaws.com/default_avatars/v1/photo_1.png', \
30
+ # subject: 'Sample Avatar', link_url: 'https://seesaw.co', link_title: 'Seesaw')
31
+ def create_choice_for_image(user_id, decision_id, options = {})
32
+ raise ArgumentError.new('image_url option is required') unless options[:image_url]
33
+
34
+ data = {
35
+ choice: {
36
+ type: 'image',
37
+ image_url: options[:image_url],
38
+ subject: options[:subject],
39
+ meta: {
40
+ link_url: options[:link_url],
41
+ link_title: options[:link_title]
42
+ }
43
+ }
44
+ }
45
+
46
+ post "users/#{user_id}/decisions/#{decision_id}/choices", data
47
+ end
48
+
49
+ # Create a new choice upload. Resulting hash contains an #upload parameter
50
+ # providing keys necessary to perform a signed S3 upload.
51
+ #
52
+ # @param user_id [Numeric] The choice's decision's user id
53
+ # @param decision_id [Numeric] The choice's decision's id
54
+ # @return [Hashie::Mash]
55
+ # @example
56
+ # Seesaw.create_choice_upload(1, 1)
57
+ def create_choice_upload(user_id, decision_id)
58
+ post "users/#{user_id}/decisions/#{decision_id}/choices"
59
+ end
60
+
61
+ # Destroy a choice
62
+ #
63
+ # @param user_id [Numeric] The choice's decision's user id
64
+ # @param decision_id [Numeric] The choice's decision's id
65
+ # @param choice_id [Numeric] The choice's id
66
+ # @return [Boolean] True if follow was successful, false otherwise.
67
+ # @example
68
+ # Seesaw.destroy_choice(1, 1)
69
+ def destroy_choice(user_id, decision_id, choice_id)
70
+ boolean_from_response :delete, "users/#{user_id}/decisions/#{decision_id}/choices/#{choice_id}"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,91 @@
1
+ module Totter
2
+ class Client
3
+ # Client methods for working with decisions
4
+ module Decisions
5
+ # Get a single decision
6
+ #
7
+ # @param user_id [Numeric] The decision's user id
8
+ # @param decision_id [Numeric] The decision's id
9
+ # @return [Hashie::Mash]
10
+ # @example
11
+ # Seesaw.decision(1, 1)
12
+ def decision(user_id, decision_id)
13
+ get "users/#{user_id}/decisions/#{decision_id}"
14
+ end
15
+
16
+ # Create a decision
17
+ #
18
+ # @param user_id [Numeric] A Seesaw user id
19
+ # @return [Hashie::Mash]
20
+ # @example
21
+ # Seesaw.create_decision(1)
22
+ def create_decision(user_id)
23
+ post "users/#{user_id}/decisions"
24
+ end
25
+
26
+ # Publish a decision
27
+ #
28
+ # @param user_id [Numeric] The decision's user id
29
+ # @param decision_id [Numeric] The decision's id
30
+ # @param options [Hash] Decision options
31
+ # @option options [String] :question The question for the decision
32
+ # @option options [Numeric] :latitude Latitude
33
+ # @option options [Numeric] :longitude Longitude
34
+ # @option options [Hash] :choice_attributes Additional attributes for decision choices
35
+ # @return [Hashie::Mash]
36
+ # @example
37
+ # Seesaw.publish_decision(1, 1, question: 'Why is the sky blue?')
38
+ def publish_decision(user_id, decision_id, options = {})
39
+ decision_options = {
40
+ decision: options
41
+ }
42
+
43
+ post "users/#{user_id}/decisions/#{decision_id}/publish", decision_options
44
+ end
45
+
46
+ # Destroy a decision
47
+ #
48
+ # @param user_id [Numeric] The decision's user id
49
+ # @param decision_id [Numeric] The decision's id
50
+ # @return [Boolean] True if follow was successful, false otherwise.
51
+ # @example
52
+ # Seesaw.destroy_decision(1, 1)
53
+ def destroy_decision(user_id, decision_id)
54
+ boolean_from_response :delete, "users/#{user_id}/decisions/#{decision_id}"
55
+ end
56
+
57
+ # Get decision analytics
58
+ #
59
+ # @param user_id [Numeric] The decision's user id
60
+ # @param decision_id [Numeric] The decision's id
61
+ # @return [Hashie::Mash]
62
+ # @example
63
+ # Seesaw.decision_analytics(1, 1)
64
+ def decision_analytics(user_id, decision_id)
65
+ get "users/#{user_id}/decisions/#{decision_id}/analytics"
66
+ end
67
+
68
+ # Flag a decision for content review
69
+ #
70
+ # @param user_id [Numeric] The decision's user id
71
+ # @param decision_id [Numeric] The decision's id
72
+ # @return [Boolean] True if follow was successful, false otherwise.
73
+ # @example
74
+ # Seesaw.flag_decision(1, 1)
75
+ def flag_decision(user_id, decision_id)
76
+ boolean_from_response :post, "users/#{user_id}/decisions/#{decision_id}/flag"
77
+ end
78
+
79
+ # Unflag a decision for content review
80
+ #
81
+ # @param user_id [Numeric] The decision's user id
82
+ # @param decision_id [Numeric] The decision's id
83
+ # @return [Boolean] True if follow was successful, false otherwise.
84
+ # @example
85
+ # Seesaw.unflag_decision(1, 1)
86
+ def unflag_decision(user_id, decision_id)
87
+ boolean_from_response :post, "users/#{user_id}/decisions/#{decision_id}/unflag"
88
+ end
89
+ end
90
+ end
91
+ end