answers-ruby-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +2 -0
  3. data/.gitignore +23 -0
  4. data/.rbenv-version +1 -0
  5. data/.travis.yml +3 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +24 -0
  8. data/README.md +120 -0
  9. data/Rakefile +6 -0
  10. data/answers-ruby-client.gemspec +34 -0
  11. data/lib/answers-ruby-client.rb +14 -0
  12. data/lib/answers/answer.rb +40 -0
  13. data/lib/answers/base_model.rb +79 -0
  14. data/lib/answers/client.rb +111 -0
  15. data/lib/answers/error.rb +4 -0
  16. data/lib/answers/protocol.rb +40 -0
  17. data/lib/answers/question.rb +38 -0
  18. data/lib/answers/version.rb +3 -0
  19. data/spec/answer_spec.rb +115 -0
  20. data/spec/cassettes/Answers_Answer/When_API_keys_are_not_provided/DELETE_destroy/throws_an_Answers_Error.yml +97 -0
  21. data/spec/cassettes/Answers_Answer/When_API_keys_are_not_provided/GET_index/contains_objects_only_of_type_Answer_Answer.yml +90 -0
  22. data/spec/cassettes/Answers_Answer/When_API_keys_are_not_provided/GET_index/retrieves_a_list_of_Answers.yml +90 -0
  23. data/spec/cassettes/Answers_Answer/When_API_keys_are_not_provided/GET_show/retrieves_a_single_object_of_type_Answers_Answer.yml +52 -0
  24. data/spec/cassettes/Answers_Answer/When_API_keys_are_not_provided/POST_create/throws_an_Answers_Error.yml +48 -0
  25. data/spec/cassettes/Answers_Answer/When_API_keys_are_not_provided/PUT_update/throws_an_Answers_Error.yml +97 -0
  26. data/spec/cassettes/Answers_Answer/When_API_keys_are_provided/DELETE_destroy/deletes_an_Answer_Answer.yml +209 -0
  27. data/spec/cassettes/Answers_Answer/When_API_keys_are_provided/POST_create/creates_an_Answer_Answer.yml +54 -0
  28. data/spec/cassettes/Answers_Answer/When_API_keys_are_provided/PUT_update/updates_an_Answer_Answer.yml +107 -0
  29. data/spec/cassettes/Answers_Question/When_API_keys_are_not_provided/DELETE_destroy/throws_an_Answers_Error.yml +97 -0
  30. data/spec/cassettes/Answers_Question/When_API_keys_are_not_provided/GET_index/contains_objects_only_of_type_Answer_Question.yml +75 -0
  31. data/spec/cassettes/Answers_Question/When_API_keys_are_not_provided/GET_index/retrieves_a_list_of_Questions.yml +75 -0
  32. data/spec/cassettes/Answers_Question/When_API_keys_are_not_provided/GET_show/retrieves_a_single_object_of_type_Answers_Question.yml +52 -0
  33. data/spec/cassettes/Answers_Question/When_API_keys_are_not_provided/POST_create/throws_an_Answers_Error.yml +48 -0
  34. data/spec/cassettes/Answers_Question/When_API_keys_are_not_provided/PUT_update/throws_an_Answers_Error.yml +97 -0
  35. data/spec/cassettes/Answers_Question/When_API_keys_are_provided/DELETE_destroy/deletes_an_Answer_Question.yml +209 -0
  36. data/spec/cassettes/Answers_Question/When_API_keys_are_provided/POST_create/creates_an_Answer_Question.yml +54 -0
  37. data/spec/cassettes/Answers_Question/When_API_keys_are_provided/PUT_update/updates_an_Answer_Question.yml +107 -0
  38. data/spec/client_spec.rb +80 -0
  39. data/spec/question_spec.rb +124 -0
  40. data/spec/spec_helper.rb +22 -0
  41. metadata +259 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: baa9d4aa429677971fc5b480e3e707cbfed9b1ce
4
+ data.tar.gz: 7423cfd98e65fb3c5dc193bcbda66279d5da54f8
5
+ SHA512:
6
+ metadata.gz: 0d57390aaea67e5c27465e2cac93764a4081681ed81f316d253a4542f65118b6f87ea0f88e509ae9df1949b70a3fc78f40280e71c716db2d6aff7853df1376d4
7
+ data.tar.gz: 55e1a087acd6fe116e5db5b7f65f4c5004e9800ad4ee4980cb1f4f4d9e05277a3b902502458f420cf5450c4abdc14403e08b62f74439d9580740165f49bb4361
@@ -0,0 +1,2 @@
1
+ ANSWERS_USER_EMAIL=your@username.example
2
+ ANSWERS_USER_TOKEN=your_user_tOkEn
@@ -0,0 +1,23 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .env
@@ -0,0 +1 @@
1
+ 2.1.2
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.1
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in answers-ruby-client.gemspec
4
+ gemspec
5
+
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
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 NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org>
@@ -0,0 +1,120 @@
1
+ [![Build Status](https://travis-ci.org/18F/answers-ruby-client.svg?branch=master)](https://travis-ci.org/18F/answers-ruby-client) [![Code Climate](https://codeclimate.com/github/18F/answers-ruby-client.png)](https://codeclimate.com/github/18F/answers-ruby-client) [![Coverage Status](https://coveralls.io/repos/18F/answers-ruby-client/badge.png)](https://coveralls.io/r/18F/answers-ruby-client)
2
+
3
+ # answers-ruby-client
4
+
5
+ Low level Ruby access to the [Answers Platform](https://github.com/18F/answers) API
6
+
7
+ ## Installation
8
+
9
+ ```ruby
10
+ gem 'answers-ruby-client'
11
+ ```
12
+
13
+ ```sh
14
+ gem install answers-ruby-client
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Synopsis
20
+
21
+ ```ruby
22
+ require 'answers'
23
+
24
+ Answers.init
25
+
26
+ questions = Answers::Question.all
27
+ questions.each do |question|
28
+ p question.id
29
+ p question.text
30
+ end
31
+
32
+ answers = Answers::Answer.all
33
+ answers.each do |answer|
34
+ p answer.id
35
+ p answer.text
36
+ p answer.question_id
37
+ q = Answers::Question.find(question.id)
38
+ p q.text
39
+ end
40
+
41
+ # write API (requires authentication)
42
+
43
+ Answers.init({
44
+ user_email: 'person@email.tld',
45
+ user_token: '1234567890qwertyuiop'
46
+ })
47
+
48
+ question = Answers::Question.new
49
+ question.text = 'Is this a question?'
50
+ question.save
51
+
52
+ answer = Answers::Answer.new
53
+ answer.text = 'Yes, this is a question.'
54
+ answer.question_id = question.id
55
+ answer.save
56
+
57
+ ```
58
+
59
+ ### CRUD
60
+
61
+ #### Create
62
+
63
+ ```ruby
64
+ Answers::Question.new(text: 'hello').save
65
+ Answers::Answer.new(text: 'hello').save
66
+ ```
67
+
68
+ #### Read
69
+
70
+ ```ruby
71
+ Answers.Question.find(1)
72
+ Answers.Answer.find(1)
73
+ ```
74
+
75
+ #### Update
76
+
77
+ ```ruby
78
+ a = Answers::Question.find(1)
79
+ a.text = 'new_text'
80
+ a.save
81
+ ```
82
+
83
+ #### Delete
84
+
85
+ ```ruby
86
+ a = Answers::Question.find(1)
87
+ a.text = 'new_text'
88
+ a.delete
89
+ ```
90
+
91
+ ### Question attributes
92
+
93
+ - `id` (`Integer`)
94
+ - `created_at` (`String`)
95
+ - `updated_at` (`String`)
96
+ - `text` (`String`) - the text of the question
97
+ - `in_language` (`String`) - the language of the question
98
+ - `need_to_know` (`String`)
99
+
100
+
101
+ ### Answer
102
+
103
+ - `id` (`Integer`)
104
+ - `created_at` (`String`)
105
+ - `updated_at` (`String`)
106
+ - `text` (`String`) - the text of the answer
107
+ - `in_language` (`String`) - the language of the answer
108
+ - `question_id` (`Integer`) - the id of the question that the Answer belongs to
109
+
110
+ ## Contributing
111
+
112
+ 1. Fork it ( https://github.com/18F/answers-ruby-client/fork )
113
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
114
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
115
+ 4. Push to the branch (`git push origin my-new-feature`)
116
+ 5. Create a new Pull Request
117
+
118
+ # License
119
+
120
+ Public Domain. See LICENSE.txt.
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'answers/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "answers-ruby-client"
9
+ spec.version = Answers::VERSION
10
+ spec.authors = ["Alan deLevie"]
11
+ spec.email = ["alan.delevie@gsa.gov"]
12
+ spec.summary = %q{Low level Ruby client to access the Answers Platform API.}
13
+ spec.description = %q{Access the the Answers Platform's read/write API in idiomatic Ruby.}
14
+ spec.homepage = ""
15
+ spec.license = "Public Domain (see LICENSE.txt)"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec", "~> 3.0.0"
25
+ spec.add_development_dependency "pry"
26
+ spec.add_development_dependency "dotenv"
27
+ spec.add_development_dependency "coveralls"
28
+ spec.add_development_dependency "webmock"
29
+ spec.add_development_dependency "vcr"
30
+
31
+ spec.add_dependency "faraday"
32
+ spec.add_dependency "faraday_middleware"
33
+ spec.add_dependency "activesupport"
34
+ end
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'answers/protocol'
5
+ require 'answers/client'
6
+ require 'answers/version'
7
+ require 'answers/error'
8
+ require 'answers/base_model'
9
+
10
+ require 'answers/question'
11
+ require 'answers/answer'
12
+
13
+ module Answers
14
+ end
@@ -0,0 +1,40 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Answers
4
+ class Answer < BaseModel
5
+
6
+ ANSWER_ATTRS = [
7
+ {name: :id, read_only: true},
8
+ {name: :created_at, read_only: true},
9
+ {name: :updated_at, read_only: true},
10
+ {name: :text, read_only: false},
11
+ {name: :in_language, read_only: false},
12
+ {name: :question_id, read_only: false}
13
+ ]
14
+
15
+ ANSWER_ATTRS.each do |attribute|
16
+ attr_fn = attribute[:read_only] ? :attr_reader : :attr_accessor
17
+ send(attr_fn, attribute[:name])
18
+ end
19
+
20
+ def initialize(params={})
21
+ ANSWER_ATTRS.each do |attribute|
22
+ instance_variable_set("@#{attribute[:name]}", params[attribute[:name]]) if params[attribute[:name]]
23
+ end
24
+ update_attributes!(params)
25
+ end
26
+
27
+ def attributes
28
+ ivar_to_sym = Proc.new {|ivar| ivar.to_s.sub(/^@/, '').to_sym}
29
+
30
+ attributes = instance_variables.inject({}) do |r, s|
31
+ r.merge!({ivar_to_sym[s] => instance_variable_get(s)})
32
+ end.delete_if do |k,v|
33
+ !ANSWER_ATTRS.map {|attribute| attribute[:name]}.include?(ivar_to_sym[k])
34
+ end
35
+
36
+ attributes
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,79 @@
1
+ module Answers
2
+ class BaseModel
3
+
4
+ def new?
5
+ @id.nil?
6
+ end
7
+
8
+ def self.resource_name
9
+ to_s.demodulize.downcase.to_sym
10
+ end
11
+
12
+ def resource_name
13
+ self.class.resource_name
14
+ end
15
+
16
+ def self.hash_key
17
+ self.resource_name.to_s.pluralize
18
+ end
19
+
20
+ def hash_key
21
+ self.class.hash_key
22
+ end
23
+
24
+ def self.find(id)
25
+ response = Answers.client.get(Protocol.uri(resource_name, id))
26
+
27
+ if response.has_key?('status')
28
+ if response['status']
29
+ return nil
30
+ end
31
+ end
32
+
33
+ item_hash = response[hash_key].first
34
+ item = new(item_hash)
35
+
36
+ item
37
+ end
38
+
39
+ def self.all
40
+ response = Answers.client.get(Protocol.uri(resource_name))
41
+ items = response[hash_key]
42
+
43
+ items.map {|i| new(i)}
44
+ end
45
+
46
+ def save
47
+ body = attributes.delete_if {|k,v| v.nil?}
48
+ path = new? ? Protocol.uri(resource_name) : Protocol.uri(resource_name, @id)
49
+ method = new? ? :post : :put
50
+
51
+ response = Answers.client.send(method, path, body)
52
+ item_hash = response[hash_key].first
53
+
54
+ update_attributes!(item_hash)
55
+
56
+ self
57
+ end
58
+
59
+ def delete
60
+ raise Answers::Error.new("Cannot delete unsaved object.") if new?
61
+ response = Answers.client.delete(Protocol.uri(resource_name, @id))
62
+
63
+ response
64
+ end
65
+
66
+ private
67
+
68
+ def update_attributes!(attributes)
69
+ attributes.each_pair do |key, value|
70
+ update_attribute!(key.to_sym, value)
71
+ end
72
+ end
73
+
74
+ def update_attribute!(attribute_name, attribute_value)
75
+ instance_variable_set("@#{attribute_name}", attribute_value)
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,111 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ require 'answers/protocol'
5
+
6
+ module Answers
7
+
8
+ def self.init(config = {})
9
+ # check env vars by default
10
+ defaulted = {
11
+ user_email: ENV['ANSWERS_USER_EMAIL'],
12
+ user_token: ENV['ANSWERS_USER_TOKEN']
13
+ }
14
+ # if credentials are passed, overwrite the defaults
15
+ defaulted.merge!(config)
16
+
17
+ # instantiate a client
18
+ @@client = Client.new(config)
19
+ end
20
+
21
+ def self.client?
22
+ return false if (defined?(@@client)).nil?
23
+ return false if @@client.nil?
24
+
25
+ true
26
+ end
27
+
28
+ # erases the current client, if one exists
29
+ def self.reset!
30
+ return if !client?
31
+ #return if (defined?(@@client)).nil?
32
+ @@client = nil
33
+ return
34
+ end
35
+
36
+ # initialized client accessible from the Answers singleton
37
+ def self.client
38
+ if !client?
39
+ raise Answers::Error.new "Answers Platform API not initialized. Call Answers.init."
40
+ end
41
+
42
+ @@client
43
+ end
44
+
45
+ class Client
46
+ attr_reader :connection
47
+
48
+ def initialize(config = {})
49
+ # use the default
50
+ url = Protocol::BASE_PATH
51
+
52
+ # or use the provided url, if one is provided
53
+ if config[:url]
54
+ url = config[:url]
55
+ end
56
+
57
+ # put it in a hash
58
+ faraday_defaults = {
59
+ url: url
60
+ }
61
+
62
+ @connection = Faraday.new(faraday_defaults) do |faraday|
63
+ #faraday.response :logger # log requests to STDOUT
64
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
65
+ faraday.headers['Content-Type'] = Protocol::DEFAULT_CONTENT_TYPE
66
+ faraday.headers[Protocol::EMAIL_HEADER_KEY] = config[:user_email] if config[:user_email]
67
+ faraday.headers[Protocol::TOKEN_HEADER_KEY] = config[:user_token] if config[:user_token]
68
+ end
69
+ end
70
+
71
+ def request(method, path, &block)
72
+ response = @connection.send(method) do |req|
73
+ req.url(path)
74
+ block.call(req) if block_given?
75
+ end
76
+
77
+ handle_response(response)
78
+ end
79
+
80
+ def get(path, params=nil)
81
+ request(:get, path) do |req|
82
+ req.params = params if params
83
+ end
84
+ end
85
+
86
+ def post(path, body=nil)
87
+ request(:post, path) do |req|
88
+ req.body = body.to_json if body
89
+ end
90
+ end
91
+
92
+ def put(path, body=nil)
93
+ request(:put, path) do |req|
94
+ req.body = body.to_json if body
95
+ end
96
+ end
97
+
98
+ def delete(path)
99
+ request(:delete, path)
100
+ end
101
+
102
+ def handle_response(response)
103
+ if response.status == 401
104
+ raise Answers::Error.new "401 Unauthorized"
105
+ end
106
+ JSON.parse(response.body)
107
+ end
108
+
109
+ end
110
+
111
+ end