fieldview 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c291877633d7f4126668164ee7cc60c7c5c67bac
4
+ data.tar.gz: df16497405345a46fab2c65066eec659ffc074b7
5
+ SHA512:
6
+ metadata.gz: 883b9f63fcaf3af60958bc8407660a76608afca697fa043afae906240d6c7f2f36ed3116dea2de6c76630c3e4ba82effe08d6d11ab3e349c4cb03b962eae052d
7
+ data.tar.gz: 8d027e67cdabf34fd66a7284196c34533d82b2f002df8826b2d91b8005ae4246fb8632225f5b67041dd518c465a4826244f5e5eabc9e7a2d6927acb91d63808d
data/.gitignore ADDED
@@ -0,0 +1,52 @@
1
+ *agribotix*
2
+ *.gem
3
+ *.rbc
4
+ *.byebug*
5
+ /.config
6
+ /coverage/
7
+ /InstalledFiles
8
+ /pkg/
9
+ /spec/reports/
10
+ /spec/examples.txt
11
+ /test/tmp/
12
+ /test/version_tmp/
13
+ /tmp/
14
+
15
+ # Used by dotenv library to load environment variables.
16
+ # .env
17
+
18
+ ## Specific to RubyMotion:
19
+ .dat*
20
+ .repl_history
21
+ build/
22
+ *.bridgesupport
23
+ build-iPhoneOS/
24
+ build-iPhoneSimulator/
25
+
26
+ ## Specific to RubyMotion (use of CocoaPods):
27
+ #
28
+ # We recommend against adding the Pods directory to your .gitignore. However
29
+ # you should judge for yourself, the pros and cons are mentioned at:
30
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
31
+ #
32
+ # vendor/Pods/
33
+
34
+ ## Documentation cache and generated files:
35
+ /.yardoc/
36
+ /_yardoc/
37
+ /doc/
38
+ /rdoc/
39
+
40
+ ## Environment normalization:
41
+ /.bundle/
42
+ /vendor/bundle
43
+ /lib/bundler/man/
44
+
45
+ # for a library or gem, you might want to ignore these files since the code is
46
+ # intended to run in multiple environments; otherwise, check them in:
47
+ # Gemfile.lock
48
+ # .ruby-version
49
+ # .ruby-gemset
50
+
51
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
52
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'minitest'
7
+ gem 'webmock'
8
+ gem 'rake'
9
+ platforms :mri do
10
+ # to avoid problems, bring Byebug in on just versions of Ruby under which
11
+ # it's known to work well
12
+ if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0')
13
+ gem 'byebug'
14
+ end
15
+ end
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fieldview (0.0.0)
5
+ faraday (~> 0.9)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.5.1)
11
+ public_suffix (~> 2.0, >= 2.0.2)
12
+ byebug (9.0.6)
13
+ crack (0.4.3)
14
+ safe_yaml (~> 1.0.0)
15
+ faraday (0.12.0.1)
16
+ multipart-post (>= 1.2, < 3)
17
+ hashdiff (0.3.2)
18
+ minitest (5.8.4)
19
+ multipart-post (2.0.0)
20
+ public_suffix (2.0.5)
21
+ rake (11.3.0)
22
+ safe_yaml (1.0.4)
23
+ webmock (3.0.1)
24
+ addressable (>= 2.3.6)
25
+ crack (>= 0.3.2)
26
+ hashdiff
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ byebug
33
+ fieldview!
34
+ minitest
35
+ rake
36
+ webmock
37
+
38
+ BUNDLED WITH
39
+ 1.13.2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Paul Susmarski
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.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # FieldView API Library
2
+
3
+ The FieldView Ruby library provides convenient access to the FieldView API from applications written in the Ruby language. It includes a pre-defined set of classes for API resources that are available currently from the API. You will need to get access from a Climate Corporation representative and the interface utilize OAUTH 2.0.
4
+
5
+ ## Usage
6
+
7
+ The library needs to be configured with your FieldView account's client secret,
8
+ client id, and x-api-key which would be provided to you by a Climate
9
+ representative. Set these according to the their values
10
+
11
+ ``` ruby
12
+ require "fieldview"
13
+
14
+ FieldView.client_id = "fake-client"
15
+ FieldView.client_secret = "fake-client-..."
16
+ FieldView.x_api_key = "..."
17
+
18
+ # Required if acquiring a new auth token without refresh tokens
19
+ FieldView.redirect_uri = "http://my.local.host/path/that/users/are/directed"
20
+
21
+
22
+ # Get an Auth token
23
+ auth_token = FieldView::AuthToken.new_auth_token_with_code_from_redirect_code(<CODE>)
24
+
25
+ # Or initialize with previous information
26
+ auth_token = FieldView::AuthToken.new(
27
+ access_token: <ATOKEN>,
28
+ expiration_at: <DATETIME>,
29
+ refresh_token: <RTOKEN>,
30
+ refresh_token_expiration_at: <DATETIME>)
31
+
32
+ # Or with just auth_token (assuming it hasn't expired)
33
+ auth_token = FieldView::AuthToken.new(access_token: <ATOKEN>)
34
+
35
+ # refresh token and a new access/refresh token will be associated with the object
36
+ auth_token = FieldView::AuthToken.new(refresh_token: <RTOKEN>)
37
+
38
+
39
+ ```
40
+
41
+ ## Development
42
+
43
+ Run all tests:
44
+
45
+ bundle exec rake
46
+
47
+ Run a single test suite:
48
+
49
+ bundle exec ruby -Ilib/ test/field_view_test.rb
50
+
51
+ Run a single test:
52
+
53
+ bundle exec ruby -Ilib/ test/field_view_test.rb -n /client.id/
54
+
55
+ ## Disclaimer
56
+
57
+ This Gem is in no way associated with The Climate Corporation, and they are in no way associated with it's support, maintenance, or updates.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = './test/**/test_*.rb'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
data/fieldview.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = 'fieldview'
5
+ s.version = "0.0.0"
6
+ s.required_ruby_version = '>= 1.9.3'
7
+ s.summary = 'Ruby bindings for the FieldView API'
8
+ s.description = ' FieldView is used make data-driven decisions to maximize your return on every acre.'
9
+ s.author = 'Paul Susmarski'
10
+ s.email = 'paul@susmarski.com'
11
+ s.homepage = 'http://rubygems.org/gems/fielview'
12
+ s.license = 'MIT'
13
+
14
+ s.add_dependency('faraday', '~> 0.9')
15
+
16
+ s.files = Dir['lib/**/*.rb']
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- test/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+ end
@@ -0,0 +1,111 @@
1
+ module FieldView
2
+ class AuthToken
3
+ attr_accessor :access_token, :access_token_expiration_at, :refresh_token, :refresh_token_expiration_at
4
+ attr_accessor :last_request
5
+ def initialize(params)
6
+ # Assume now was 5 seconds ago
7
+ now = FieldView.get_now_for_auth_token - 5
8
+
9
+ # Used on every request
10
+ self.access_token = params[:access_token]
11
+
12
+ # When the access token expires we'll need to refresh,
13
+ # can be specified as part of the object, 14399 is what is being
14
+ # returned at the time of writing this (2017-04-11)
15
+ self.access_token_expiration_at = params[:access_token_expiration_at] || (now + (params[:expires_in]||14399) - 10)
16
+
17
+ # Refresh token isn't required, but can be initialized with this
18
+ self.refresh_token = params[:refresh_token]
19
+
20
+ # Refresh token technically expires in 30 days, but
21
+ # we'll subtract one day for safety
22
+ self.refresh_token_expiration_at = params[:access_token_expiration_at] || (now + (30-1)*24*60*60)
23
+ end
24
+
25
+ def access_token_expired?
26
+ return !!(self.access_token.nil? || self.access_token_expiration_at <= Time.now)
27
+ end
28
+
29
+ def refresh_token_expired?
30
+ return !!(self.refresh_token.nil? || self.refresh_token_expiration_at <= Time.now)
31
+ end
32
+
33
+ def refresh_access_token!()
34
+ http, request = self.class.build_token_request([
35
+ ["grant_type", "refresh_token"],
36
+ ["refresh_token", self.refresh_token]
37
+ ])
38
+ response = http.request(request)
39
+ if response.code != "200" then
40
+ # An error has occurred, log to roll bar and notify them
41
+ raise RefreshTokenError.new("Failed to refresh FieldView token, response body: #{response.body}")
42
+ else
43
+ json = JSON.parse(response.body, symbolize_names: true)
44
+ initialize(json)
45
+ end
46
+ end
47
+
48
+ def execute_request!(method, path, headers: {}, params: {})
49
+ self.last_request = path
50
+ if access_token_expired? && refresh_token_expired? then
51
+ raise AllTokensExpiredError.new("All of your tokens have expired. " \
52
+ "You'll need to re-log into FieldView.")
53
+ end
54
+
55
+ if access_token_expired? then
56
+ refresh_access_token!()
57
+ end
58
+ uri = URI.parse("#{FieldView.api_base}#{FieldView.api_version}/#{path}")
59
+
60
+ # TODO: Handle parameters
61
+ http = Net::HTTP.new(uri.host, uri.port)
62
+ http.use_ssl = FieldView.api_base.start_with?("https")
63
+ request = Net::HTTP.class_eval(method.to_s.capitalize).new(uri.request_uri)
64
+ request["Accept"] = "*/*"
65
+ request["Authorization"] = "Bearer #{self.access_token}"
66
+ request["X-Api-Key"] = FieldView.x_api_key
67
+ request["Content-Type"] = "application/json"
68
+
69
+ headers.each do |header,value|
70
+ request[header] = value.to_s
71
+ end
72
+
73
+ response = http.request(request)
74
+ FieldView.handle_response_error_codes(response)
75
+ return FieldViewResponse.new(response)
76
+ end
77
+
78
+ def self.build_token_request(url_params)
79
+ uri = URI.parse(FieldView.oauth_token_base)
80
+ new_query_ar = URI.decode_www_form(uri.query || '')
81
+ url_params.each do |param|
82
+ new_query_ar << param
83
+ end
84
+ uri.query = URI.encode_www_form(new_query_ar)
85
+ http = Net::HTTP.new(uri.host, uri.port)
86
+ http.use_ssl = true
87
+ request = Net::HTTP::Post.new(uri.request_uri)
88
+ request["Accept"] = "*/*"
89
+ request["Authorization"] = "Basic #{Base64.encode64("#{FieldView.client_id}:#{FieldView.client_secret}").strip}"
90
+ request["Content-Type"] = "application/x-www-form-urlencoded"
91
+ return http, request
92
+ end
93
+
94
+ # Code will be collected from the redirect to your server
95
+ def self.new_auth_token_with_code_from_redirect_code(code)
96
+ http, request = build_token_request([
97
+ ["grant_type", "authorization_code"],
98
+ ["redirect_uri", FieldView.redirect_uri],
99
+ ["code", code]
100
+ ])
101
+ response = http.request(request)
102
+ if response.code != "200" then
103
+ raise AuthenticationError.new("Was unable to get a new auth token using the code provided. " \
104
+ "See response body: #{response.body}")
105
+ else
106
+ json = JSON.parse(response.body, symbolize_names: true)
107
+ return AuthToken.new(json)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,28 @@
1
+ module FieldView
2
+ class Feature
3
+ attr_accessor :type, :coordinates
4
+ def initialize(json_feature_object)
5
+ # Use RGEO??
6
+ self.type = json_feature_object[:type]
7
+ self.coordinates = json_feature_object[:coordinates]
8
+ end
9
+
10
+ def point?()
11
+ return !!(self.type =~ /\Apoint\z/i)
12
+ end
13
+
14
+ def multi_polygon?()
15
+ return !!(self.type =~ /\Amultipolygon\z/i)
16
+ end
17
+ end
18
+ class Boundary
19
+ attr_accessor :id, :units, :area, :centroid, :geometry
20
+ def initialize(json_object)
21
+ self.id = json_object[:id]
22
+ self.area = json_object[:area][:q]
23
+ self.units = json_object[:area][:u]
24
+ self.centroid = Feature.new(json_object[:centroid])
25
+ self.geometry = Feature.new(json_object[:geometry])
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,64 @@
1
+ module FieldView
2
+ # FieldViewError is the base error from which all other more specific FieldView
3
+ # errors derive.
4
+ class FieldViewError < StandardError
5
+ attr_reader :message
6
+
7
+ # These fields are now available as part of #response and that usage should
8
+ # be preferred.
9
+ attr_reader :http_body
10
+ attr_reader :http_headers
11
+ attr_reader :http_status
12
+ attr_reader :request_id
13
+
14
+ attr_accessor :response
15
+ # Initializes a FieldViewError.
16
+ def initialize(message=nil, http_status: nil, http_body: nil,
17
+ http_headers: nil)
18
+ @message = message
19
+ @http_status = http_status
20
+ @http_body = http_body
21
+ @http_headers = http_headers || {}
22
+ @request_id = @http_headers[FieldView::REQUEST_ID_HEADER_KEY]
23
+ end
24
+
25
+ def to_s
26
+ status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
27
+ id_string = @request_id.nil? ? "" : "(Request #{@request_id}) "
28
+ "#{status_string}#{id_string}#{@message}"
29
+ end
30
+ end
31
+
32
+ # AuthenticationError is raised when invalid credentials are used to connect
33
+ # to FieldView's servers.
34
+ class AuthenticationError < FieldViewError
35
+ end
36
+
37
+ # Raised when all the tokens expired and nothing can be done.
38
+ class AllTokensExpiredError < FieldViewError
39
+ end
40
+
41
+ # Raised when access resources you don't have access to
42
+ class PermissionError < FieldViewError
43
+ end
44
+
45
+ # Raised when a refresh token is attempted to be used but it can't be
46
+ class RefreshTokenError < FieldViewError
47
+ end
48
+
49
+ # Raised when too many requests are being made
50
+ class RateLimitError < FieldViewError
51
+ end
52
+
53
+ # Raised when accessing a non-existent resource
54
+ class InvalidRequestError < FieldViewError
55
+ end
56
+
57
+ # Raised when something goes wrong with FieldView
58
+ class InternalServerError < FieldViewError
59
+ end
60
+
61
+ # Raised when the server is busy
62
+ class ServerBusyError < FieldViewError
63
+ end
64
+ end
@@ -0,0 +1,22 @@
1
+ module FieldView
2
+ class Field
3
+ attr_accessor :id
4
+ attr_accessor :name
5
+ attr_accessor :boundary_id
6
+ attr_accessor :auth_token
7
+ def initialize(json_object, auth_token = nil)
8
+ self.id = json_object[:id]
9
+ self.name = json_object[:name]
10
+ self.boundary_id = json_object[:boundaryId]
11
+ self.auth_token = auth_token
12
+ end
13
+
14
+ def boundary
15
+ @boundary ||= nil
16
+ if @boundary.nil?
17
+ @boundary = Boundary.new(self.auth_token.execute_request!(:get, "boundaries/#{self.boundary_id}").data)
18
+ end
19
+ return @boundary
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ module FieldView
2
+ class Fields
3
+ PATH = "fields"
4
+ def self.list(auth_token, limit: nil, next_token: nil)
5
+ limit ||= FieldView.default_page_limit
6
+ response = auth_token.execute_request!(:get, PATH,
7
+ headers: {
8
+ FieldView::NEXT_TOKEN_HEADER_KEY => next_token,
9
+ FieldView::PAGE_LIMIT_HEADER_KEY => limit
10
+ })
11
+ next_token = response.http_headers[FieldView::NEXT_TOKEN_HEADER_KEY]
12
+
13
+ if (response.http_status == 200 || response.http_status == 206) then
14
+ # 206: Partial result, will have more data
15
+ # 200: When all the results were in the list
16
+ return_data = response.data[:results]
17
+ elsif (response.http_status == 304)
18
+ # 304: Nothing modified since last request
19
+ return_data = []
20
+ else
21
+ # This should never happen
22
+ return_data = nil
23
+ end
24
+
25
+ return ListObject.new(
26
+ self,
27
+ auth_token,
28
+ return_data.collect { |i| Field.new(i, auth_token) },
29
+ response.http_status,
30
+ next_token: next_token,
31
+ limit: limit)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ module FieldView
2
+ class FieldViewResponse
3
+ attr_accessor :request_id
4
+ attr_accessor :http_body
5
+ attr_accessor :http_headers
6
+ attr_accessor :http_status
7
+ attr_accessor :data
8
+ def initialize(response)
9
+ self.http_headers = {}
10
+ response.each_capitalized_name do |n|
11
+ self.http_headers[n] = response[n]
12
+ end
13
+
14
+ self.http_body = response.body
15
+ begin
16
+ self.data = JSON.parse(response.body, symbolize_names: true)
17
+ rescue JSON::ParserError
18
+ self.data = nil
19
+ end
20
+ self.http_status = response.code.to_i
21
+ self.request_id = self.http_headers[FieldView::REQUEST_ID_HEADER_KEY]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ module FieldView
2
+ class ListObject
3
+ attr_accessor :limit
4
+ attr_reader :auth_token, :data, :last_http_status, :next_token, :listable
5
+ include Enumerable
6
+
7
+ def initialize(listable, auth_token, data, http_status, next_token: nil, limit: 100)
8
+ @listable = listable
9
+ @auth_token = auth_token
10
+ @data = data
11
+ @last_http_status = http_status
12
+ @next_token = next_token
13
+ @limit = 100
14
+ end
15
+
16
+ def each(&blk)
17
+ self.data.each(&blk)
18
+ end
19
+
20
+ def next_page!()
21
+ return if !self.more_pages?()
22
+ new_list = @listable.list(auth_token, limit: self.limit, next_token: self.next_token)
23
+ @data = new_list.data
24
+ @last_http_status = new_list.last_http_status
25
+ @auth_token = new_list.auth_token
26
+ end
27
+
28
+ # alias for more_pages
29
+ def has_more?()
30
+ return self.more_pages?()
31
+ end
32
+
33
+ def more_pages?()
34
+ return Util.http_status_is_more_in_list?(self.last_http_status)
35
+ end
36
+
37
+ def restart!()
38
+ @last_http_status = nil
39
+ @next_token = nil
40
+ next_page!()
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ module FieldView
2
+ class Util
3
+ def self.http_status_is_more_in_list?(http_status)
4
+ return http_status.to_i == 206
5
+ end
6
+ end
7
+ end
data/lib/fieldview.rb ADDED
@@ -0,0 +1,130 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'rbconfig'
4
+ require 'base64'
5
+
6
+ # API Support Classes
7
+ require 'fieldview/errors'
8
+ require 'fieldview/auth_token'
9
+ require 'fieldview/fields'
10
+ require 'fieldview/util'
11
+ require 'fieldview/fieldview_response'
12
+ require 'fieldview/field'
13
+ require 'fieldview/list_object'
14
+ require 'fieldview/boundary'
15
+
16
+ module FieldView
17
+ @oauth_token_base = "https://api.climate.com/api/oauth/token"
18
+ @api_base = "https://platform.climate.com/v"
19
+ @max_network_retries = 0
20
+ @api_version = 4
21
+ @default_page_limit = 100
22
+ NEXT_TOKEN_HEADER_KEY = "X-Next-Token"
23
+ REQUEST_ID_HEADER_KEY = "X-Http-Request-Id"
24
+ PAGE_LIMIT_HEADER_KEY = "X-Limit"
25
+
26
+ class << self
27
+ attr_accessor :oauth_token_base, :api_base, :max_network_retries, :api_version, :now,
28
+ :default_page_limit
29
+
30
+ def get_now_for_auth_token
31
+ now || Time.now
32
+ end
33
+
34
+ def x_api_key
35
+ @_x_api_key ||= nil
36
+ unless @_x_api_key
37
+ raise AuthenticationError.new('No x-api-key provided. ' \
38
+ 'Set your x-api-key using "FieldView.x_api_key = <X-API-KEY>". ' \
39
+ 'This should have been provided to you by a Climate representative. ' \
40
+ 'This takes the form of "my-client"')
41
+ end
42
+ return @_x_api_key
43
+ end
44
+ def x_api_key=(value)
45
+ @_x_api_key = value
46
+ end
47
+
48
+ def redirect_uri
49
+ @_redirect_uri ||= nil
50
+ unless @_redirect_uri
51
+ raise AuthenticationError.new("You must set the redirect uri to your proper server " \
52
+ "to get new auth tokens if you haven't set one. " \
53
+ "Set your redirect uri using FieldView.redirect_uri = <REDIRECT_URI>")
54
+ end
55
+ return @_redirect_uri
56
+ end
57
+ def redirect_uri=(value)
58
+ @_redirect_uri = value
59
+ end
60
+
61
+ def client_id
62
+ @_client_id ||= nil
63
+ unless @_client_id
64
+ raise AuthenticationError.new('No client id provided. ' \
65
+ 'Set your client id using "FieldView.client_id = <CLIENT-ID>". ' \
66
+ 'This should have been provided to you by a Climate representative. ' \
67
+ 'This takes the form of "my-client"')
68
+ end
69
+ return @_client_id
70
+ end
71
+ def client_id=(value)
72
+ @_client_id = value
73
+ end
74
+
75
+ def client_secret
76
+ @_client_secret ||= nil
77
+ unless @_client_secret
78
+ raise AuthenticationError.new('No client_secret provided. ' \
79
+ 'Set your client secret using "FieldView.client_secret = <CLIENT-SECRET>". ' \
80
+ 'This should have been provided to you by a Climate representative. ' \
81
+ 'This takes the form of "my-client-fz9900x98-x98908j-jslx"')
82
+ end
83
+ return @_client_secret
84
+ end
85
+ def client_secret=(value)
86
+ @_client_secret = value
87
+ end
88
+
89
+ def handle_response_error_codes(response)
90
+ headers = response.to_hash
91
+ code = response.code.to_i
92
+ body = response.body
93
+ error = nil
94
+ case code
95
+ when 503
96
+ error = ServerBusyError.new(
97
+ "Server Busy", http_status: code, http_body: body,
98
+ http_headers: headers)
99
+ when 400, 404
100
+ error = InvalidRequestError.new(
101
+ "Bad input", http_status: code, http_body: body,
102
+ http_headers: headers)
103
+ when 401
104
+ error = AuthenticationError.new(
105
+ "Unauthorized", http_status: code, http_body: body,
106
+ http_headers: headers)
107
+ when 403
108
+ error = PermissionError.new(
109
+ "Forbidden", http_status: code, http_body: body,
110
+ http_headers: headers)
111
+ when 429
112
+ # Retry-After will be in the headers
113
+ error = RateLimitError.new(
114
+ "Too many requests", http_status: code, http_body: body,
115
+ http_headers: headers)
116
+ when 500
117
+ error = InternalServerError.new(
118
+ "Internal server error", http_status: code, http_body: body,
119
+ http_headers: headers)
120
+ end
121
+
122
+ if error.nil? then
123
+ return
124
+ else
125
+ error.response = response
126
+ raise error
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,141 @@
1
+ {
2
+ "boundary_one": {
3
+ "id": "e694b43e-86eb-4773-86d0-3c4f546178dc",
4
+ "area": {
5
+ "q": 58.923473472390256,
6
+ "u": "ac"
7
+ },
8
+ "centroid": {
9
+ "type": "Point",
10
+ "coordinates": [
11
+ -105.223479062131,
12
+ 40.0579906147381
13
+ ]
14
+ },
15
+ "geometry": {
16
+ "type": "MultiPolygon",
17
+ "coordinates": [
18
+ [
19
+ [
20
+ [
21
+ -105.2255165,
22
+ 40.0437726
23
+ ],
24
+ [
25
+ -105.2208496,
26
+ 40.0438021
27
+ ],
28
+ [
29
+ -105.2208915,
30
+ 40.0402151
31
+ ],
32
+ [
33
+ -105.2255049,
34
+ 40.0402473
35
+ ],
36
+ [
37
+ -105.2255165,
38
+ 40.0437726
39
+ ]
40
+ ]
41
+ ],
42
+ [
43
+ [
44
+ [
45
+ -105.2257098,
46
+ 40.087157
47
+ ],
48
+ [
49
+ -105.2257173,
50
+ 40.0903676
51
+ ],
52
+ [
53
+ -105.2249648,
54
+ 40.0898429
55
+ ],
56
+ [
57
+ -105.2232786,
58
+ 40.0894078
59
+ ],
60
+ [
61
+ -105.2226989,
62
+ 40.0886577
63
+ ],
64
+ [
65
+ -105.2218189,
66
+ 40.0883161
67
+ ],
68
+ [
69
+ -105.2218299,
70
+ 40.0879038
71
+ ],
72
+ [
73
+ -105.2217414,
74
+ 40.0874009
75
+ ],
76
+ [
77
+ -105.2214516,
78
+ 40.0871603
79
+ ],
80
+ [
81
+ -105.2257098,
82
+ 40.087157
83
+ ]
84
+ ]
85
+ ]
86
+ ]
87
+ }
88
+ },
89
+ "single_field_list": {
90
+ "results": [
91
+ {
92
+ "id": "530dd6e4-170b-4254-b10c-ef07b9533f74",
93
+ "name": "NE of Airport",
94
+ "boundaryId": "e694b43e-86eb-4773-86d0-3c4f546178dc"
95
+ }
96
+ ]
97
+ },
98
+ "field_two_list": {
99
+ "results": [
100
+ {
101
+ "id": "687c5ad6-7969-4112-8234-f8b5474a7389",
102
+ "name": "SE of Airport",
103
+ "boundaryId": "0a34b7bc-3f6b-4ace-9e6d-f97a7c6b66b9"
104
+ }
105
+ ]
106
+ },
107
+ "authorization": {
108
+ "access_token": "b2a58121-e02e-4c56-9036-ac23eb295ddc",
109
+ "token_type": "bearer",
110
+ "refresh_token": "23bba1b4-4e61-4e41-83fb-620061158239",
111
+ "expires_in": 14399,
112
+ "scope": "openid partnerapis platform",
113
+ "id_token": "8s9nxjx908uoij12oik2j4.1j4kjs90ivcoixkjmxrw3um493u8924u4yuemnshfjdsen450q.dG9rZW4iLCJhdWQiOiJwYXJ0bmVyLWFncmlib3RpeCIsImV4cCI6MTQ5MTk0NTUzNSwiaWF0IjoxNDkxOTQ0OTM1LCJhdXRoX3RpbWUiOjE0OTE5NDQ5MzUsInVzZXJfaWQiOiIxODYzNzUiLCJzdWIiOiIxODYzNzUiLCJlbWFpbCI6ImNvbXB1dGVAYWdyaWJvdGl4LmNvbSIsImFkZHJlc3MiOnt9LCJyb2xlcyI6WyJjbGllbnQiXSwicGhvbmVfbnVtYmVyIjoiMzAzNzE3ODk3OSJ9.TjSPzpdIUUQMD3wh_-PCdB2o0M8n1tpWAeicQ-Wz-AwXsbmxesDnZroppsN-0_H_8ULOhSKwlGVGEuuD6QfYElhlGZuviSZcYkwYDNtXNN9oVa4MgfWK76txLV59oaGpFuHlqKGMskL4uV5Ao_H9_0jN1THqluav-WUh4WwxB-LclIu4i7gv1g5Bw5zpzzccQAzErc04u_Gf1gwi1IZtFYJJncSnnm1hp5XhBZgLZD0twiNOYzxML3nFbM37WEf1G3iqSLiTH1bIKc_MO7dG3o8oq3SoYjDmjRWl7iL7WD3jZHWiAuvVBa0rDqOOeVpCPUYPk62n9HJ6JNtMnSp3eUvnR8nfaOvm7P1ZehOAVnJVq1yWfjOtxEvjLBmD7wRs9I5N2OANiLgqvnvJVy_BuAqQXDqmgusrU9FotGRP2obSRfZvaPzxXurCYY9OfS8uxxHBlzVe06dTNRezuOzy_op7y33dOEOzFfoBNyFcTtlptcICs6uIsGC_k239auWTiMKIGgAdtbgoAvVLK49-s7IdX9gfUv9ugaZgduPDq6hzxWQh-Uahyf1LkpLfb9wTHPdq9upW8Dt5iSBQKcngCBdLjR3WrGIRi0M0m2beRSB-acUiIWreeoCOY78UErEd5ebYwfBaAFh1Igy5EfvfYyaNjhv1-cYX8q3fc2JBPPU",
114
+ "user": {
115
+ "tosacceptedat": null,
116
+ "email": "testing@test.com",
117
+ "firstviews": 0,
118
+ "phone": "5555555555",
119
+ "roles": [
120
+ "Client"
121
+ ],
122
+ "city": null,
123
+ "firstname": "Testing",
124
+ "source": "user-service-default",
125
+ "address1": null,
126
+ "mailchimp": false,
127
+ "address2": null,
128
+ "zip": null,
129
+ "id": 186375,
130
+ "state": null,
131
+ "lastname": "Lastname",
132
+ "uuid": "ca9cdad5-c759-44b5-9442-371338f301a9",
133
+ "enabled": true,
134
+ "country": "United States",
135
+ "asOf": 1491944935932
136
+ },
137
+ "site": {
138
+ "siteCode": "fccd95c45c048f7c8c9a60c8"
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,26 @@
1
+ class APIFixtures
2
+ def initialize
3
+ @fixtures = ::JSON.parse(File.read("#{PROJECT_ROOT}/spec/fixtures.json"),
4
+ symbolize_names: true)
5
+ freeze_recursively(@fixtures)
6
+ end
7
+
8
+ def [](name)
9
+ @fixtures[name]
10
+ end
11
+
12
+ def fetch(*args)
13
+ @fixtures.fetch(*args)
14
+ end
15
+
16
+ private
17
+
18
+ def freeze_recursively(data)
19
+ data.each do |k, v|
20
+ if v.is_a?(Hash)
21
+ freeze_recursively(v)
22
+ end
23
+ end
24
+ data.freeze
25
+ end
26
+ end
@@ -0,0 +1,91 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class TestAuthToken < Minitest::Test
4
+ FIXTURE = API_FIXTURES.fetch(:authorization)
5
+ def auth_uri_stub
6
+ "#{prefix_for_all_requests}#{FieldView.oauth_token_base[8..-1]}"
7
+ end
8
+
9
+ def check_token_matches_fixture(token)
10
+ assert_equal FIXTURE[:access_token], token.access_token
11
+ assert_equal FIXTURE[:refresh_token], token.refresh_token
12
+ assert_equal FieldView.now + FIXTURE[:expires_in] - 15, token.access_token_expiration_at
13
+ assert_equal FieldView.now - 5 + 29*24*60*60, token.refresh_token_expiration_at, "Should be 29 days in the future"
14
+ end
15
+
16
+ def setup
17
+ setup_for_api_requests
18
+ end
19
+
20
+ def teardown
21
+ teardown_for_api_request
22
+ end
23
+
24
+ def test_build_token_request
25
+ http, request = FieldView::AuthToken.build_token_request([["dont", "care"]])
26
+ assert_equal "/api/oauth/token?dont=care", request.path
27
+
28
+ headers = request.to_hash
29
+ assert_equal ["*/*"], headers["accept"]
30
+ assert_equal ["Basic #{Base64.encode64("#{FieldView.client_id}:#{FieldView.client_secret}").strip}"], headers["authorization"]
31
+ assert_equal ["application/x-www-form-urlencoded"], headers["content-type"]
32
+
33
+ assert_equal Net::HTTP::Post, request.class
34
+ assert_equal "api.climate.com", http.address
35
+ end
36
+
37
+ def test_new_auth_token_with_code_from_redirect_code_bad
38
+ stub_request(:post, "#{auth_uri_stub}?" \
39
+ "code&grant_type=authorization_code&redirect_uri=#{FieldView.redirect_uri}").
40
+ to_return(status: 401, body: JSON.generate({}))
41
+ assert_raises FieldView::AuthenticationError do
42
+ FieldView::AuthToken.new_auth_token_with_code_from_redirect_code(nil)
43
+ end
44
+ end
45
+
46
+ def test_new_auth_token_with_code_from_redirect_code_good
47
+ # now a good token
48
+ code = "code"
49
+ FieldView.now = Time.now
50
+ stub_request(:post, "#{auth_uri_stub}?" \
51
+ "code=#{code}&grant_type=authorization_code&redirect_uri=#{FieldView.redirect_uri}").
52
+ to_return(status: 200, body: JSON.generate(FIXTURE))
53
+ token = FieldView::AuthToken.new_auth_token_with_code_from_redirect_code(code)
54
+ check_token_matches_fixture(token)
55
+ end
56
+
57
+ def test_create_with_just_access_token
58
+ token = FieldView::AuthToken.new(access_token: "yyy")
59
+ assert !token.access_token_expired?
60
+ assert token.refresh_token_expired?
61
+ end
62
+
63
+ def test_create_with_just_refresh_token
64
+ FieldView.now = Time.now
65
+ token = FieldView::AuthToken.new(refresh_token: "xxx")
66
+ assert token.access_token_expired?
67
+ assert !token.refresh_token_expired?
68
+
69
+ stub_request(:post, "#{auth_uri_stub}?" \
70
+ "grant_type=refresh_token&refresh_token=#{token.refresh_token}").
71
+ to_return(status: 200, body: JSON.generate(FIXTURE))
72
+ token.refresh_access_token!
73
+ check_token_matches_fixture(token)
74
+ end
75
+
76
+ def test_completely_expired_token
77
+ token = FieldView::AuthToken.new(
78
+ access_token: "yyy",
79
+ refresh_token: "xxx",
80
+ refresh_token_expiration_at: Time.now - 20,
81
+ access_token_expiration_at: Time.now - 20
82
+ )
83
+
84
+ assert token.access_token_expired?
85
+ assert token.refresh_token_expired?
86
+
87
+ assert_raises FieldView::AllTokensExpiredError do
88
+ token.execute_request!(:get, "dont/care")
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class TestBoundary < Minitest::Test
4
+ FIXTURE = API_FIXTURES.fetch(:boundary_one)
5
+ def test_initialization
6
+ boundary = FieldView::Boundary.new(FIXTURE)
7
+
8
+ assert_equal FIXTURE[:id], boundary.id
9
+ assert_equal 58.923473472390256, boundary.area
10
+ assert_equal "ac", boundary.units
11
+ assert_equal 2, boundary.centroid.coordinates.length
12
+ assert boundary.centroid.point?
13
+ assert_equal 2, boundary.geometry.coordinates.length
14
+ assert boundary.geometry.multi_polygon?
15
+ assert boundary.geometry.multi_polygon? != boundary.centroid.multi_polygon?, "make sure they are different types"
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class TestField < Minitest::Test
4
+ FIXTURE = API_FIXTURES.fetch(:single_field_list)[:results][0]
5
+ def test_initialization
6
+ field = FieldView::Field.new(FIXTURE, new_auth_token)
7
+
8
+ assert_equal FIXTURE[:id], field.id
9
+ assert_equal FIXTURE[:name], field.name
10
+ assert_equal FIXTURE[:boundaryId], field.boundary_id
11
+ assert_equal new_auth_token.access_token, field.auth_token.access_token
12
+ end
13
+
14
+ def test_get_boundary
15
+ field = FieldView::Field.new(FIXTURE, new_auth_token)
16
+
17
+ stub_request(:get, /boundaries\/#{API_FIXTURES[:boundary_one][:id]}/).
18
+ to_return(status: 200, body: API_FIXTURES[:boundary_one].to_json())
19
+
20
+ api_requests() do
21
+ field.boundary
22
+ end
23
+
24
+ assert_equal FIXTURE[:boundaryId], field.boundary.id
25
+ end
26
+ end
@@ -0,0 +1,70 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class TestFields < Minitest::Test
4
+ FIXTURE = API_FIXTURES.fetch(:single_field_list)
5
+
6
+ def setup
7
+ setup_for_api_requests
8
+ end
9
+
10
+ def teardown
11
+ teardown_for_api_request
12
+ end
13
+
14
+ def test_list_with_one_page()
15
+ next_token = "AZXJKLA123"
16
+ stub_request(:get, /fields/).
17
+ with(headers: { FieldView::PAGE_LIMIT_HEADER_KEY => FieldView.default_page_limit.to_s}).
18
+ to_return(status: 200, body: FIXTURE.to_json(),
19
+ headers: next_token_headers)
20
+ fields = FieldView::Fields.list(new_auth_token)
21
+
22
+ assert_equal FIXTURE[:results].length, fields.data.length
23
+ assert_equal FIXTURE[:results][0][:id], fields.data[0].id
24
+ assert_equal next_token, fields.next_token
25
+ assert_equal FieldView::Fields, fields.listable
26
+ end
27
+
28
+ def test_list_with_no_more_data
29
+ stub_request(:get, /fields/).
30
+ with(headers: { FieldView::PAGE_LIMIT_HEADER_KEY => FieldView.default_page_limit.to_s}).
31
+ to_return(status: 304, body: {}.to_json(),
32
+ headers: next_token_headers)
33
+ fields = FieldView::Fields.list(new_auth_token)
34
+
35
+ assert_equal 0, fields.data.length
36
+ end
37
+
38
+ def test_with_more_data
39
+ next_token = "AZXJKLA123"
40
+ stub_request(:get, /fields/).
41
+ with(headers: { FieldView::PAGE_LIMIT_HEADER_KEY => FieldView.default_page_limit.to_s}).
42
+ to_return(status: 200, body: FIXTURE.to_json(),
43
+ headers: next_token_headers)
44
+ fields = FieldView::Fields.list(new_auth_token)
45
+
46
+ assert_equal FIXTURE[:results].length, fields.data.length
47
+ assert_equal FIXTURE[:results][0][:id], fields.data[0].id
48
+ assert_equal next_token, fields.next_token
49
+ end
50
+
51
+ def test_with_limit
52
+ stub_request(:get, /fields/).
53
+ with(headers: { FieldView::PAGE_LIMIT_HEADER_KEY => 1}).
54
+ to_return(status: 200, body: FIXTURE.to_json(),
55
+ headers: next_token_headers)
56
+ fields = FieldView::Fields.list(new_auth_token, limit: 1)
57
+
58
+ assert_equal 1, fields.data.length
59
+ end
60
+
61
+ def test_with_next_token
62
+ next_token = "AZXJKLA123"
63
+ stub_request(:get, /fields/).
64
+ with(headers: { FieldView::NEXT_TOKEN_HEADER_KEY => next_token }).
65
+ to_return(status: 200, body: FIXTURE.to_json())
66
+
67
+ fields = FieldView::Fields.list(new_auth_token, next_token: next_token)
68
+ assert_equal 1, fields.data.length
69
+ end
70
+ end
@@ -0,0 +1,46 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class TestFieldView < Minitest::Test
4
+ def test_now
5
+ assert (Time.now - FieldView.get_now_for_auth_token) <= 5, "should be close"
6
+ expected_time = Time.now - 20
7
+ FieldView.now = expected_time
8
+ assert_equal expected_time, FieldView.get_now_for_auth_token
9
+ end
10
+
11
+ def test_requires_client_id
12
+ FieldView.client_id = nil
13
+ assert_raises FieldView::AuthenticationError do
14
+ FieldView.client_id
15
+ end
16
+ FieldView.client_id = "test"
17
+ assert FieldView.client_id
18
+ end
19
+
20
+ def test_requires_client_secret
21
+ FieldView.client_secret = nil
22
+ assert_raises FieldView::AuthenticationError do
23
+ FieldView.client_secret
24
+ end
25
+ FieldView.client_secret = "test"
26
+ assert FieldView.client_secret
27
+ end
28
+
29
+ def test_requires_redirect_uri
30
+ FieldView.redirect_uri = nil
31
+ assert_raises FieldView::AuthenticationError do
32
+ FieldView.redirect_uri
33
+ end
34
+ FieldView.redirect_uri = "test"
35
+ assert FieldView.redirect_uri
36
+ end
37
+
38
+ def test_requires_x_api_key
39
+ FieldView.x_api_key = nil
40
+ assert_raises FieldView::AuthenticationError do
41
+ FieldView.x_api_key
42
+ end
43
+ FieldView.x_api_key = "test"
44
+ assert FieldView.x_api_key
45
+ end
46
+ end
@@ -0,0 +1,52 @@
1
+ require 'minitest/autorun'
2
+ require 'fieldview'
3
+ require 'byebug'
4
+ require 'webmock/minitest'
5
+
6
+ PROJECT_ROOT = File.expand_path("../../", __FILE__)
7
+ require File.expand_path('../api_fixtures', __FILE__)
8
+
9
+ class Minitest::Test
10
+ # Fixtures are available in tests using something like:
11
+ #
12
+ # API_FIXTURES[:fields][:id]
13
+ #
14
+ API_FIXTURES = APIFixtures.new
15
+
16
+
17
+ def next_token_headers(next_token = "AZXJKLA123")
18
+ return {FieldView::NEXT_TOKEN_HEADER_KEY => next_token}
19
+ end
20
+
21
+ def new_auth_token
22
+ FieldView::AuthToken.new(
23
+ access_token: "yyy",
24
+ refresh_token: "xxx"
25
+ )
26
+ end
27
+
28
+ def api_requests()
29
+ setup_for_api_requests
30
+ yield
31
+ teardown_for_api_request
32
+ end
33
+
34
+ def setup_for_api_requests()
35
+ FieldView.redirect_uri = "http://fake.test/"
36
+ FieldView.client_id = "fake-client"
37
+ FieldView.client_secret = "fake-client-abcdef12-3456-7890-abcd-ef0123456789"
38
+ FieldView.x_api_key = "ixj98c98njkn109su9jxkjks61"
39
+ end
40
+
41
+ def teardown_for_api_request()
42
+ FieldView.redirect_uri = nil
43
+ FieldView.client_id = nil
44
+ FieldView.client_secret = nil
45
+ FieldView.x_api_key = nil
46
+ FieldView.now = nil
47
+ end
48
+
49
+ def prefix_for_all_requests
50
+ "https://"
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class TestListObject < Minitest::Test
4
+ FIXTURE = API_FIXTURES.fetch(:single_field_list)
5
+
6
+ def test_each_loop
7
+ data = ["x","y","z"]
8
+ list = FieldView::ListObject.new(FieldView::Fields, new_auth_token, data, 200, next_token: nil)
9
+
10
+ list.each_with_index do |x, i|
11
+ assert_equal data[i], x
12
+ end
13
+ end
14
+
15
+ def test_get_next_page
16
+ next_token = "JZIOJKLJ"
17
+ list = FieldView::ListObject.new(FieldView::Fields, new_auth_token,
18
+ ["dont","care"], 206, next_token: next_token)
19
+ stub_request(:get, /fields/).
20
+ with(headers: next_token_headers(next_token)).
21
+ to_return(status: 200, body: API_FIXTURES[:field_two_list].to_json(),
22
+ headers: next_token_headers())
23
+
24
+ api_requests() do
25
+ list.next_page!()
26
+ end
27
+
28
+ assert_equal 1, list.data.length
29
+ assert_equal API_FIXTURES[:field_two_list][:results][0][:id], list.data[0].id
30
+ assert !list.more_pages?()
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fieldview
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Susmarski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0.9'
27
+ description: ' FieldView is used make data-driven decisions to maximize your return
28
+ on every acre.'
29
+ email: paul@susmarski.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE
38
+ - README.md
39
+ - Rakefile
40
+ - fieldview.gemspec
41
+ - lib/fieldview.rb
42
+ - lib/fieldview/auth_token.rb
43
+ - lib/fieldview/boundary.rb
44
+ - lib/fieldview/errors.rb
45
+ - lib/fieldview/field.rb
46
+ - lib/fieldview/fields.rb
47
+ - lib/fieldview/fieldview_response.rb
48
+ - lib/fieldview/list_object.rb
49
+ - lib/fieldview/util.rb
50
+ - spec/fixtures.json
51
+ - test/api_fixtures.rb
52
+ - test/test_auth_token.rb
53
+ - test/test_boundary.rb
54
+ - test/test_field.rb
55
+ - test/test_fields.rb
56
+ - test/test_fieldview.rb
57
+ - test/test_helper.rb
58
+ - test/test_list_object.rb
59
+ homepage: http://rubygems.org/gems/fielview
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - '>='
70
+ - !ruby/object:Gem::Version
71
+ version: 1.9.3
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.0.14.1
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: Ruby bindings for the FieldView API
83
+ test_files:
84
+ - test/api_fixtures.rb
85
+ - test/test_auth_token.rb
86
+ - test/test_boundary.rb
87
+ - test/test_field.rb
88
+ - test/test_fields.rb
89
+ - test/test_fieldview.rb
90
+ - test/test_helper.rb
91
+ - test/test_list_object.rb