myjohndeere 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +52 -0
  3. data/Gemfile +16 -0
  4. data/Gemfile.lock +37 -0
  5. data/LICENSE +21 -0
  6. data/README.md +109 -0
  7. data/Rakefile +8 -0
  8. data/lib/myjohndeere.rb +132 -0
  9. data/lib/myjohndeere/access_token.rb +79 -0
  10. data/lib/myjohndeere/api_support_item.rb +27 -0
  11. data/lib/myjohndeere/boundary.rb +18 -0
  12. data/lib/myjohndeere/core_ext/string.rb +9 -0
  13. data/lib/myjohndeere/errors.rb +10 -0
  14. data/lib/myjohndeere/field.rb +28 -0
  15. data/lib/myjohndeere/file_resource.rb +61 -0
  16. data/lib/myjohndeere/hash_utils.rb +33 -0
  17. data/lib/myjohndeere/json_attributes.rb +39 -0
  18. data/lib/myjohndeere/list_object.rb +94 -0
  19. data/lib/myjohndeere/map_layer.rb +41 -0
  20. data/lib/myjohndeere/map_layer_summary.rb +40 -0
  21. data/lib/myjohndeere/map_legend_item.rb +10 -0
  22. data/lib/myjohndeere/metadata_item.rb +8 -0
  23. data/lib/myjohndeere/organization.rb +16 -0
  24. data/lib/myjohndeere/organization_owned_resource.rb +17 -0
  25. data/lib/myjohndeere/requestable.rb +36 -0
  26. data/lib/myjohndeere/response.rb +31 -0
  27. data/lib/myjohndeere/rest_methods.rb +64 -0
  28. data/lib/myjohndeere/single_resource.rb +13 -0
  29. data/lib/myjohndeere/util.rb +54 -0
  30. data/lib/myjohndeere/version.rb +3 -0
  31. data/myjohndeere.gemspec +23 -0
  32. data/spec/fixtures.json +778 -0
  33. data/test/api_fixtures.rb +29 -0
  34. data/test/test_access_token.rb +60 -0
  35. data/test/test_boundary.rb +37 -0
  36. data/test/test_field.rb +47 -0
  37. data/test/test_file_resource.rb +48 -0
  38. data/test/test_helper.rb +36 -0
  39. data/test/test_list_object.rb +74 -0
  40. data/test/test_map_layer.rb +52 -0
  41. data/test/test_map_layer_summary.rb +52 -0
  42. data/test/test_myjohndeere.rb +56 -0
  43. data/test/test_organization.rb +42 -0
  44. data/test/test_requestable.rb +15 -0
  45. data/test/test_rest_methods.rb +34 -0
  46. data/test/test_util.rb +86 -0
  47. metadata +118 -0
@@ -0,0 +1,27 @@
1
+ module MyJohnDeere
2
+ class APISupportItem
3
+ include JSONAttributes
4
+ # see attributes_to_pull_from_json for the order if creating yourself
5
+ def initialize(args)
6
+ if args.length == 1 then
7
+ # assume the json object was passed
8
+ setup_attributes(args[0])
9
+ else
10
+ # otherwise assume normal construction according to the order for the json attributes
11
+ self.class.json_attributes.each_with_index do |attribute, i|
12
+ underscored = attribute.to_s.underscore
13
+ raise ArgumentError("You must pass #{attribute} as argument #{i+1}") if self.class.json_attributes.length <= i
14
+ self.send("#{underscored}=", args[i])
15
+ end
16
+ end
17
+ end
18
+
19
+ def to_hash()
20
+ ret_hash = {}
21
+ self.class.json_attributes.each do |attrib|
22
+ ret_hash[attrib] = self.send(attrib.to_s.underscore)
23
+ end
24
+ return ret_hash
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ module MyJohnDeere
2
+ class Boundary < OrganizationOwnedResource
3
+ self.base_jd_resource = "boundaries"
4
+ self.list_resource_path = "organizations/%{organization_id}/fields/%{field_id}/#{self.base_jd_resource}"
5
+ self.retrieve_resource_path = "organizations/%{organization_id}/#{self.base_jd_resource}"
6
+ attributes_to_pull_from_json(:id, :name, :multipolygons, :active)
7
+ attr_accessor :field_id
8
+
9
+ def initialize(json_object, access_token = nil, field_id = nil)
10
+ super(json_object, access_token)
11
+ self.field_id = field_id
12
+ self.active = self.active.to_s.downcase == "true"
13
+ self.multipolygons = json_object["multipolygons"]
14
+ # This doesn't exist currently, not sure why
15
+ self.field_id ||= extract_link_with_rel_from_list("fields", /\/(\d+)\/(.+?)\/fields\Z/)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+ def underscore
3
+ self.gsub(/::/, '/').
4
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
5
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
6
+ tr("-", "_").
7
+ downcase
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module MyJohnDeere
2
+ # MyJohnDeereError is the base error from which all other more specific MyJohnError
3
+ # errors derive.
4
+ class MyJohnDeereError < StandardError
5
+ end
6
+
7
+ # Configuration error is raised when configuration hasn't been properly done
8
+ class ConfigurationError < MyJohnDeereError
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module MyJohnDeere
2
+ class Field < OrganizationOwnedResource
3
+ self.base_jd_resource = "fields"
4
+ self.list_resource_path = "organizations/%{organization_id}/#{self.base_jd_resource}"
5
+ self.retrieve_resource_path = "organizations/%{organization_id}/#{self.base_jd_resource}"
6
+ attributes_to_pull_from_json(:id, :name, :boundaries)
7
+
8
+ def initialize(json_object, access_token = nil)
9
+ super(json_object, access_token)
10
+ boundaries = json_object["boundaries"]
11
+ if boundaries && boundaries.length > 0 then
12
+ # If we embed, then we'll need to pass our id
13
+ self.boundary = Boundary.new(boundaries[0], access_token, self.id)
14
+ end
15
+ end
16
+
17
+ def boundary
18
+ if @boundary.nil? then
19
+ @boundary = Boundary.retrieve(self.access_token, field_id: self.id, organization_id: self.organization_id)
20
+ end
21
+ return @boundary
22
+ end
23
+
24
+ def boundary=(val)
25
+ @boundary = val
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,61 @@
1
+ module MyJohnDeere
2
+ class FileResource < OrganizationOwnedResource
3
+ self.base_jd_resource = "fileResources"
4
+ self.list_resource_path = "mapLayers/%{map_layer_id}/#{self.base_jd_resource}"
5
+ self.retrieve_resource_path = self.base_jd_resource
6
+ attributes_to_pull_from_json(:id, :filename, :mimeType, :metadata, :timestamp)
7
+
8
+ def initialize(json_object, access_token = nil)
9
+ super(json_object, access_token)
10
+ end
11
+
12
+ def self.create(access_token, organization_id, map_layer_id, file_type: nil,
13
+ metadata: [])
14
+ raise ArgumentError.new("You must pass a file_type") if file_type.nil?
15
+
16
+ case file_type
17
+ when :png
18
+ mime_type = 'image/png'
19
+ when :zip
20
+ mime_type = 'application/zip'
21
+ else
22
+ raise ArgumentError.new("You must specify either a zip or a png")
23
+ end
24
+
25
+ body = {
26
+ links: [
27
+ self.owning_organization_link_item(organization_id)
28
+ ],
29
+ mimeType: mime_type,
30
+ metadata: metadata.map { |m| m.to_hash }
31
+ }
32
+
33
+ response = access_token.execute_request(:post,
34
+ build_resouce_base_path!(self.list_resource_path, {map_layer_id: map_layer_id}),
35
+ body: body
36
+ )
37
+ #{"Content-Type"=>"text/plain", "X-Deere-Handling-Server"=>"ldxtc3", "X-Frame-Options"=>"SAMEORIGIN", "Location"=>"https://sandboxapi.deere.com/platform/mapLayers/e2711205-c5df-445e-aad5-81eaf9090e6c", "X-Deere-Elapsed-Ms"=>"162", "Vary"=>"Accept-Encoding", "Expires"=>"Thu, 14 Sep 2017 15:52:24 GMT", "Cache-Control"=>"max-age=0, no-cache", "Pragma"=>"no-cache", "Date"=>"Thu, 14 Sep 2017 15:52:24 GMT", "Transfer-Encoding"=>"chunked", "Connection"=>"close, Transfer-Encoding"}
38
+ id = get_created_id_from_response_headers(self.base_jd_resource, response)
39
+ if id.nil? then
40
+ return nil
41
+ else
42
+ return self.new(HashUtils.deep_stringify_keys({"id" => id}.merge(body)))
43
+ end
44
+ end
45
+
46
+ def self.upload_file(access_token, file_resource_id, file_path)
47
+ File.open(file_path, "rb:UTF-8") do |f|
48
+ body = f.read()
49
+ response = access_token.execute_request(:put,
50
+ "#{self.base_jd_resource}/#{file_resource_id}",
51
+ body: body,
52
+ headers: {
53
+ 'accept'=> 'application/vnd.deere.axiom.v3+json',
54
+ "Content-Type"=>'application/octet-stream' ,
55
+ "Content-Length" => body.bytesize.to_s
56
+ })
57
+ return response.code == 204
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,33 @@
1
+ module MyJohnDeere
2
+ class HashUtils
3
+ def self.transform_hash(original, options={}, &block)
4
+ original.inject({}){|result, (key,value)|
5
+ value = if (options[:deep] && Hash === value)
6
+ transform_hash(value, options, &block)
7
+ else
8
+ if Array === value
9
+ value.map{|v| transform_hash(v, options, &block)}
10
+ else
11
+ value
12
+ end
13
+ end
14
+ block.call(result,key,value)
15
+ result
16
+ }
17
+ end
18
+
19
+ # Convert keys to strings
20
+ def self.stringify_keys(in_hash)
21
+ transform_hash(in_hash) {|hash, key, value|
22
+ hash[key.to_s] = value
23
+ }
24
+ end
25
+
26
+ # Convert keys to strings, recursively
27
+ def self.deep_stringify_keys(in_hash)
28
+ transform_hash(in_hash, :deep => true) {|hash, key, value|
29
+ hash[key.to_s] = value
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ module MyJohnDeere
2
+ module JSONAttributes
3
+ module ClassMethods
4
+ attr_accessor :json_attributes
5
+ def attributes_to_pull_from_json(*attribs)
6
+ self.json_attributes = attribs
7
+ self.json_attributes.each do |attribute|
8
+ attribute = attribute.to_s.underscore
9
+ define_method("#{attribute}=") do |val|
10
+ instance_variable_set("@#{attribute}", val)
11
+ end
12
+ define_method(attribute) do
13
+ return instance_variable_get("@#{attribute}")
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ def setup_attributes(json_data)
21
+ return if self.class.json_attributes.nil?
22
+ self.class.json_attributes.each do |attrib|
23
+ attrib = attrib.to_s
24
+ val = json_data[attrib]
25
+ if /(date)|(timestamp)/i.match(attrib) then
26
+ # try to parse it
27
+ val = Time.parse(val) rescue val
28
+ end
29
+ instance_variable_set("@#{attrib.underscore}", val)
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.included(receiver)
35
+ receiver.extend ClassMethods
36
+ receiver.send :include, InstanceMethods
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,94 @@
1
+ module MyJohnDeere
2
+ class ListObject < Requestable
3
+ OPTION_ATTRIBUTES = [:count, :start, :etag]
4
+ attr_reader :data, :listable, :total, :options
5
+ include Enumerable
6
+
7
+ OPTION_ATTRIBUTES.each do |attribute|
8
+ define_method("#{attribute}=") do |val|
9
+ options[attribute] = val
10
+ end
11
+ define_method(attribute) do
12
+ return options[attribute]
13
+ end
14
+ end
15
+
16
+ def initialize(listable, access_token, json_data,
17
+ options: {})
18
+ @options = options
19
+ # Confirm object is listable?
20
+ @listable = listable
21
+ @data = json_data["values"].collect { |i| listable.new(i, access_token) }
22
+ if self.using_etag?
23
+ MyJohnDeere.logger.info("Using etag, ignoring any specification about start/count")
24
+ self.start = 0
25
+ self.count = @data.length
26
+ else
27
+ MyJohnDeere.logger.info("Etag omitted using start/count")
28
+ self.start ||= 0
29
+ self.count ||= 10
30
+ end
31
+ # Total is the total record count as specified by the john deere response
32
+ @total = json_data["total"] || data.length
33
+ super(json_data, access_token)
34
+ end
35
+
36
+ def each(&blk)
37
+ self.data.each(&blk)
38
+ end
39
+
40
+ def next_page!()
41
+ return if !self.has_more?()
42
+ new_list = @listable.list(self.access_token,
43
+ count: self.count,
44
+ start: self.start + self.count,
45
+ etag: self.etag)
46
+ new_list.instance_variables.each do |iv|
47
+ self.instance_variable_set(iv, new_list.instance_variable_get(iv))
48
+ end
49
+ end
50
+
51
+ def has_more?()
52
+ return !self.using_etag? && self.start + self.data.length < self.total
53
+ end
54
+
55
+ def using_etag?
56
+ # will be equal "" or some other string
57
+ return !self.etag.nil?
58
+ end
59
+ end
60
+ end
61
+ # def iterate_through_john_deere_resource(john_deere_resource_path, options={})
62
+ # options = {
63
+ # headers: nil,
64
+ # body: ""
65
+ # }.merge(options)
66
+ # loop do
67
+ # response = self.request_against_access_token(:get, john_deere_resource_path, options)
68
+
69
+ # case response.code.to_s
70
+ # when "304" # Not modified, the headers won't change either
71
+ # logger.info("JohnDeere iteration not modified: #{john_deere_resource_path}")
72
+ # break
73
+ # when "200" # Success
74
+ # # now make the json and yield it to the block
75
+ # response_json = JSON.parse(response.body)
76
+
77
+ # yield(response_json, response.to_hash)
78
+
79
+ # # see if we have another page, will either be nil or the hash, which will have ["uri"]
80
+ # next_uri = response_json["links"].find { |link| link["rel"] == "nextPage" }
81
+ # full_resource_uri = next_uri.try(:[], "uri")
82
+ # break if full_resource_uri.nil?
83
+
84
+ # # convert it to a regular path so we don't have to mess with the
85
+ # # full route
86
+ # john_deere_resource_path = URI.parse(full_resource_uri).path
87
+
88
+ # # one final check
89
+ # break if john_deere_resource_path.nil?
90
+ # else
91
+ # break
92
+ # end
93
+ # end
94
+ # end
@@ -0,0 +1,41 @@
1
+ module MyJohnDeere
2
+ class MapLayer < OrganizationOwnedResource
3
+ self.base_jd_resource = "mapLayers"
4
+ self.list_resource_path = "mapLayerSummaries/%{map_layer_summary_id}/#{self.base_jd_resource}"
5
+ self.retrieve_resource_path = self.base_jd_resource
6
+ attributes_to_pull_from_json(:id, :title, :extent, :legends)
7
+
8
+ def initialize(json_object, access_token = nil)
9
+ super(json_object, access_token)
10
+ self.legends["ranges"].map! { |l| MapLegendItem.new(l) }
11
+ end
12
+
13
+ def self.create(access_token, map_layer_summary_id, organization_id,
14
+ title: "", minimum_latitude: 0, maximum_latitude: 0,
15
+ minimum_longitude: 0, maximum_longitude:0, map_layer_id: "", map_legend_items: [])
16
+ body = {
17
+ links: [
18
+ self.owning_organization_link_item(organization_id)
19
+ ],
20
+ title: "The title on the map layer",
21
+ extent: {
22
+ minimumLatitude: minimum_latitude,
23
+ maximumLatitude: maximum_latitude,
24
+ minimumLongitude: minimum_longitude,
25
+ maximumLongitude: maximum_longitude
26
+ },
27
+ legends: {
28
+ unitId: map_layer_id,
29
+ ranges: map_legend_items.map { |mls| mls.to_hash }
30
+ }
31
+ }
32
+
33
+ response = access_token.execute_request(:post,
34
+ build_resouce_base_path!(self.list_resource_path, {map_layer_summary_id: map_layer_summary_id}),
35
+ body: body
36
+ )
37
+ #{"Content-Type"=>"text/plain", "X-Deere-Handling-Server"=>"ldxtc3", "X-Frame-Options"=>"SAMEORIGIN", "Location"=>"https://sandboxapi.deere.com/platform/mapLayers/e2711205-c5df-445e-aad5-81eaf9090e6c", "X-Deere-Elapsed-Ms"=>"162", "Vary"=>"Accept-Encoding", "Expires"=>"Thu, 14 Sep 2017 15:52:24 GMT", "Cache-Control"=>"max-age=0, no-cache", "Pragma"=>"no-cache", "Date"=>"Thu, 14 Sep 2017 15:52:24 GMT", "Transfer-Encoding"=>"chunked", "Connection"=>"close, Transfer-Encoding"}
38
+ return get_created_id_from_response_headers(self.base_jd_resource, response)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ module MyJohnDeere
2
+ class MapLayerSummary < OrganizationOwnedResource
3
+ self.base_jd_resource = "mapLayerSummaries"
4
+ self.list_resource_path = "organizations/%{organization_id}/fields/%{field_id}/#{self.base_jd_resource}"
5
+ self.retrieve_resource_path = self.base_jd_resource
6
+ attributes_to_pull_from_json(:id, :title, :text, :metadata, :dateCreated, :lastModifiedDate)
7
+
8
+ def initialize(json_object, access_token = nil)
9
+ super(json_object, access_token)
10
+ end
11
+
12
+ def self.create(access_token, organization_id, field_id,
13
+ title, caption, metadata = [], date_created=nil)
14
+ body = {
15
+ title: title,
16
+ text: caption,
17
+ links: [
18
+ owning_organization_link_item(organization_id),
19
+ {
20
+ rel: "contributionDefinition",
21
+ uri: "#{MyJohnDeere.configuration.endpoint}/#{MyJohnDeere.configuration.contribution_definition_id}"
22
+ }
23
+ ],
24
+ metadata: metadata.map { |md| md.to_hash },
25
+ dateCreated: (date_created || Time.now).strftime("%Y-%m-%dT%H:%M:%S.%LZ")
26
+ }
27
+ response = access_token.execute_request(:post,
28
+ build_resouce_base_path!(self.list_resource_path, {field_id: field_id, organization_id: organization_id}),
29
+ body: body
30
+ )
31
+ #{"Content-Type"=>"text/plain", "X-Deere-Handling-Server"=>"ldxtc4", "X-Frame-Options"=>"SAMEORIGIN", "Location"=>"https://sandboxapi.deere.com/platform/mapLayerSummaries/c5e9317e-eda6-48d3-acc8-c3bca3424858", "X-Deere-Elapsed-Ms"=>"362", "Vary"=>"Accept-Encoding", "Expires"=>"Wed, 13 Sep 2017 22:00:45 GMT", "Cache-Control"=>"max-age=0, no-cache", "Pragma"=>"no-cache", "Date"=>"Wed, 13 Sep 2017 22:00:45 GMT", "Transfer-Encoding"=>"chunked", "Connection"=>"close, Transfer-Encoding"}
32
+ id = get_created_id_from_response_headers(self.base_jd_resource, response)
33
+ if id.nil?
34
+ return nil
35
+ else
36
+ return self.new(HashUtils.deep_stringify_keys({"id" => id}.merge(body)))
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,10 @@
1
+ module MyJohnDeere
2
+ class MapLegendItem < APISupportItem
3
+ attributes_to_pull_from_json(:label, :minimum, :maximum, :hexColor, :percent)
4
+
5
+ # see attributes_to_pull_from_json for the order if creating yourself
6
+ def initialize(*args)
7
+ super(args)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module MyJohnDeere
2
+ class MetadataItem < APISupportItem
3
+ attributes_to_pull_from_json(:key, :value)
4
+ def initialize(*args)
5
+ super(args)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ module MyJohnDeere
2
+ class Organization < SingleResource
3
+ self.base_jd_resource = "organizations"
4
+ self.list_resource_path = self.base_jd_resource
5
+ self.retrieve_resource_path = self.base_jd_resource
6
+ attributes_to_pull_from_json(:id, :name, :type, :member)
7
+
8
+ def initialize(json_object, access_token = nil)
9
+ super(json_object, access_token)
10
+ end
11
+
12
+ def fields
13
+ return MyJohnDeere::Field.list(self.access_token, organization_id: self.id)
14
+ end
15
+ end
16
+ end