restful-sharepoint 0.1.7 → 0.2.2
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.
- checksums.yaml +4 -4
- data/lib/restful-sharepoint/collection.rb +29 -14
- data/lib/restful-sharepoint/common-base.rb +1 -14
- data/lib/restful-sharepoint/connection.rb +29 -15
- data/lib/restful-sharepoint/object.rb +42 -34
- data/lib/restful-sharepoint/version.rb +1 -1
- data/spec/restfulsharepoint_spec.rb +56 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 676e57e07950be0b111e91a9675056ab7f9b3d90c39319dc1e9ac619476e7cec
|
4
|
+
data.tar.gz: 717a90610e1a58b0895eed3fe018f3d99faae5f7947ce1b81797c0e8cf9fe6c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e448c10bb59bee93d58725f109f2c5ce8f55ed0b2bae7f27944ae865ec9352f1f133947a948f5fadce119b67c614a844f7f6c1435ac31cab6fafff9c7134a472
|
7
|
+
data.tar.gz: dd7afb07ce1d26484e3f48a070c8dfe28c67de1a52c02481b5a13d70f90d15e0e35ae91a4e4d545c98669402a02b20513dc6c43d7a35c59e1a6dd4cf637ea332
|
@@ -2,13 +2,18 @@ module RestfulSharePoint
|
|
2
2
|
class Collection < CommonBase
|
3
3
|
DEFAULT_OPTIONS = {}
|
4
4
|
|
5
|
+
include Enumerable
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@collection, :each, :length, :[]
|
9
|
+
|
5
10
|
def self.object_class
|
6
11
|
Object
|
7
12
|
end
|
8
13
|
|
9
14
|
def initialize(parent: nil, connection: nil, collection: nil, options: {})
|
10
15
|
@parent = parent
|
11
|
-
@connection =
|
16
|
+
@connection = @parent ? @parent.connection : connection
|
12
17
|
self.collection = collection
|
13
18
|
self.options = options
|
14
19
|
end
|
@@ -22,24 +27,42 @@ module RestfulSharePoint
|
|
22
27
|
|
23
28
|
attr_writer :endpoint
|
24
29
|
def endpoint
|
25
|
-
@endpoint || (raise NotImplementedError, "Endpoint
|
30
|
+
@endpoint || (raise NotImplementedError, "Endpoint could not be determined")
|
26
31
|
end
|
27
32
|
|
28
33
|
def collection=(collection)
|
29
34
|
@collection = collection
|
30
35
|
@collection&.each_with_index do |v,i|
|
31
|
-
@collection[i] = objectify(v)
|
36
|
+
@collection[i] = connection.objectify(v)
|
32
37
|
end
|
33
|
-
@
|
38
|
+
@collection
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(other)
|
42
|
+
other.== collection
|
43
|
+
end
|
44
|
+
|
45
|
+
def eql?(other)
|
46
|
+
other.eql? collection
|
34
47
|
end
|
35
48
|
|
36
49
|
def collection
|
37
50
|
@collection || self.collection = connection.get(endpoint, options: @options)
|
38
51
|
end
|
39
52
|
|
40
|
-
def
|
41
|
-
collection.
|
53
|
+
def to_a
|
54
|
+
collection.map do |v|
|
55
|
+
case v
|
56
|
+
when Object
|
57
|
+
v.to_h
|
58
|
+
when Collection
|
59
|
+
v.to_a
|
60
|
+
else
|
61
|
+
v
|
62
|
+
end
|
63
|
+
end
|
42
64
|
end
|
65
|
+
alias to_array to_a
|
43
66
|
|
44
67
|
def next
|
45
68
|
self.new(@connection, @connection.get(collection['__next']))
|
@@ -48,13 +71,5 @@ module RestfulSharePoint
|
|
48
71
|
def to_json(*args, &block)
|
49
72
|
collection.to_json(*args, &block)
|
50
73
|
end
|
51
|
-
|
52
|
-
def method_missing(method, *args, &block)
|
53
|
-
collection.respond_to?(method) ? collection.send(method, *args, &block) : super
|
54
|
-
end
|
55
|
-
|
56
|
-
def respond_to_missing?(method, include_all = false)
|
57
|
-
collection.respond_to?(method, include_all)
|
58
|
-
end
|
59
74
|
end
|
60
75
|
end
|
@@ -1,18 +1,5 @@
|
|
1
1
|
module RestfulSharePoint
|
2
2
|
class CommonBase
|
3
|
-
|
4
|
-
def objectify(tree)
|
5
|
-
if tree['results'] && !tree['results'].empty?
|
6
|
-
type = tree.dig('__metadata', 'type') || tree.dig('results', 0, '__metadata', 'type')
|
7
|
-
raise "Object type not specified" unless type
|
8
|
-
pattern, klass = COLLECTION_MAP.find { |pattern,| pattern.match(type) }
|
9
|
-
klass ? RestfulSharePoint.const_get(klass).new(parent: self, collection: tree['results']) : tree['results']
|
10
|
-
elsif tree['__metadata']
|
11
|
-
type = tree['__metadata']['type']
|
12
|
-
raise "Collection type not specified" unless type
|
13
|
-
pattern, klass = OBJECT_MAP.find { |pattern,| pattern.match(type) }
|
14
|
-
klass ? RestfulSharePoint.const_get(klass).new(parent: self, properties: tree) : tree
|
15
|
-
end
|
16
|
-
end
|
3
|
+
|
17
4
|
end
|
18
5
|
end
|
@@ -13,6 +13,10 @@ module RestfulSharePoint
|
|
13
13
|
|
14
14
|
attr_reader :site_url
|
15
15
|
|
16
|
+
def get_as_object(path, options: {})
|
17
|
+
objectify(get(path, options: options))
|
18
|
+
end
|
19
|
+
|
16
20
|
def get(path, options: {})
|
17
21
|
request path, :get, options: options
|
18
22
|
end
|
@@ -27,7 +31,7 @@ module RestfulSharePoint
|
|
27
31
|
req = HTTPI::Request.new(url: url, headers: {'accept' => 'application/json; odata=verbose'})
|
28
32
|
req.auth.ntlm(@username, @password) if @username
|
29
33
|
if body
|
30
|
-
req.body = body.
|
34
|
+
req.body = JSON.dump(body).gsub('/', '\\/') # SharePoint requires forward slashes be escaped in JSON (WTF!!!)
|
31
35
|
req.headers['Content-Type'] = 'application/json'
|
32
36
|
req.headers['X-HTTP-Method'] = 'MERGE' # TODO: Extend logic to support all operations
|
33
37
|
req.headers['If-Match'] = '*'
|
@@ -35,35 +39,45 @@ module RestfulSharePoint
|
|
35
39
|
yield(req) if block_given?
|
36
40
|
LOG.info "Making HTTP request to: #{req.url.to_s}"
|
37
41
|
response = HTTPI.request(method, req)
|
38
|
-
# Log each request to file
|
39
|
-
if @debug
|
40
|
-
request_name = req.url.path.gsub(/[^\w.]/, '_')
|
41
|
-
filepath = ::File.join(@debug, "#{DateTime.now.strftime('%FT%R')}_#{request_name}.log")
|
42
|
-
FileUtils.mkdir_p @debug
|
43
|
-
::File.open(filepath, 'w+') do |file|
|
44
|
-
file.puts response.code
|
45
|
-
response.headers.each { |k,v| file.puts "#{k}: #{v}" }
|
46
|
-
file.puts '----------'
|
47
|
-
file.puts response.body
|
48
|
-
end
|
49
|
-
end
|
50
42
|
if response.body.empty?
|
51
43
|
if response.code >= 300
|
52
44
|
raise RestError, "Server returned HTTP status #{response.code} with no message body."
|
53
45
|
end
|
54
46
|
else
|
55
47
|
if response.headers['Content-Type'].start_with? "application/json"
|
56
|
-
|
48
|
+
parse response.body
|
57
49
|
else
|
58
50
|
response.body
|
59
51
|
end
|
60
52
|
end
|
61
53
|
end
|
62
54
|
|
55
|
+
# Converts the given enumerable tree to a collection or object.
|
56
|
+
def objectify(tree, parent: nil)
|
57
|
+
if tree['results']
|
58
|
+
type = tree['__metadata']&.[]('type') || tree['results'][0]&.[]('__metadata')&.[]('type') || ''
|
59
|
+
pattern, klass = COLLECTION_MAP.any? { |pattern,| pattern.match(type) }
|
60
|
+
klass ||= :Collection
|
61
|
+
RestfulSharePoint.const_get(klass).new(connection: self, parent: parent, collection: tree['results'])
|
62
|
+
elsif tree['__metadata']
|
63
|
+
type = tree['__metadata']['type']
|
64
|
+
raise "Object type not specified. #{tree.inspect}" unless type
|
65
|
+
pattern, klass = OBJECT_MAP.find { |pattern,| pattern.match(type) }
|
66
|
+
klass ? RestfulSharePoint.const_get(klass).new(connection: self, parent: parent, properties: tree) : tree
|
67
|
+
else
|
68
|
+
tree
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def objectified?(v)
|
73
|
+
CommonBase === v || !v.is_a?(Hash)
|
74
|
+
end
|
75
|
+
|
63
76
|
protected
|
64
77
|
|
65
78
|
def parse(str)
|
66
|
-
data = JSON.
|
79
|
+
data = JSON.load(str)
|
80
|
+
::File.write('parsed.json', JSON.dump(data))
|
67
81
|
raise RestError, "(#{data['error']['code']}): #{data['error']['message']['value']}" if data['error']
|
68
82
|
parse_tree(data['d'])
|
69
83
|
end
|
@@ -2,10 +2,15 @@ module RestfulSharePoint
|
|
2
2
|
class Object < CommonBase
|
3
3
|
DEFAULT_OPTIONS = {}
|
4
4
|
|
5
|
+
include Enumerable
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :properties, :length, :keys
|
9
|
+
|
5
10
|
def initialize(parent: nil, connection: nil, properties: nil, id: nil, options: {})
|
6
11
|
raise Error, "Either a parent or connection must be provided." unless parent || connection
|
7
12
|
@parent = parent
|
8
|
-
@connection =
|
13
|
+
@connection = @parent ? @parent.connection : connection
|
9
14
|
self.properties = properties
|
10
15
|
@id = id
|
11
16
|
self.options = options
|
@@ -19,58 +24,61 @@ module RestfulSharePoint
|
|
19
24
|
|
20
25
|
attr_writer :endpoint
|
21
26
|
def endpoint
|
27
|
+
@endpoint || self['__metadata']['uri'] || (raise NotImplementedError, "Endpoint could not be determined")
|
28
|
+
end
|
22
29
|
|
30
|
+
attr_writer :properties
|
31
|
+
def properties
|
32
|
+
@properties || self.properties = connection.get(endpoint, options: @options)
|
23
33
|
end
|
24
34
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
warn("`options` have been ignored as `#{k}` has already been loaded") unless options.empty?
|
34
|
-
properties[k]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
elsif v.respond_to?(:keys) && (v['__metadata'] || v['results'])
|
38
|
-
@properties[k] = objectify(v)
|
39
|
-
end
|
35
|
+
def [](key, options = {})
|
36
|
+
if connection.objectified?(properties[key])
|
37
|
+
warn "`options` have been ignored as deferred object has already been fetched" unless options.empty?
|
38
|
+
properties[key]
|
39
|
+
elsif properties[key].respond_to?('[]') && properties[key]['__deferred']
|
40
|
+
properties[key] = fetch_deferred(key, options)
|
41
|
+
else
|
42
|
+
properties[key] = connection.objectify(properties[key])
|
40
43
|
end
|
41
|
-
@properties
|
42
44
|
end
|
43
45
|
|
44
|
-
def
|
45
|
-
|
46
|
+
def ==(other)
|
47
|
+
other.== properties
|
48
|
+
end
|
49
|
+
|
50
|
+
def eql?(other)
|
51
|
+
other.eql? properties
|
46
52
|
end
|
47
53
|
|
48
|
-
def
|
49
|
-
|
54
|
+
def to_h
|
55
|
+
hash = {}
|
56
|
+
properties.each do |k,v|
|
57
|
+
hash[k] = case v
|
58
|
+
when Object
|
59
|
+
v.to_h
|
60
|
+
when Collection
|
61
|
+
v.to_a
|
62
|
+
else
|
63
|
+
v
|
64
|
+
end
|
65
|
+
end
|
50
66
|
end
|
67
|
+
alias to_hash to_h
|
51
68
|
|
52
69
|
def fetch_deferred(property, options = {})
|
53
|
-
|
54
|
-
@properties[property] = objectify(data)
|
70
|
+
connection.get_as_object(@properties[property]['__deferred']['uri'], options: options)
|
55
71
|
end
|
56
72
|
|
57
73
|
def to_json(*args, &block)
|
58
74
|
properties.to_json(*args, &block)
|
59
75
|
end
|
60
76
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
elsif self.methods(false).include?(method) # Works around lazily loaded `properties`
|
65
|
-
self.send(method, *args, &block)
|
66
|
-
else
|
67
|
-
super
|
77
|
+
def each(&block)
|
78
|
+
properties.each do |k,v|
|
79
|
+
yield k, self[k]
|
68
80
|
end
|
69
81
|
end
|
70
82
|
|
71
|
-
def respond_to_missing?(method, include_all = false)
|
72
|
-
properties.respond_to?(method, include_all)
|
73
|
-
end
|
74
|
-
|
75
83
|
end
|
76
84
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
ENV['RUBY_ENV'] = 'production'
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require_relative '../lib/restful-sharepoint.rb'
|
5
|
+
|
6
|
+
CONFIG = YAML.load_file(File.join(__dir__, 'config.yml'))
|
7
|
+
|
8
|
+
module RestfulSharePoint
|
9
|
+
describe RestfulSharePoint do
|
10
|
+
let :c do
|
11
|
+
Connection.new(CONFIG[:site_url], CONFIG[:username], CONFIG[:password])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can establish a connection" do
|
15
|
+
lists = c.get('/_api/web/lists/')
|
16
|
+
expect(lists).to be_a(Hash)
|
17
|
+
expect(lists['results']).to be_a(Array)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "can work with a collection" do
|
21
|
+
lists = Collection.new(connection: c, collection: c.get('/_api/web/lists/')['results'])
|
22
|
+
expect(lists.map { |v| v['Title'] }).to include(CONFIG[:test_list_title])
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can work with an object" do
|
26
|
+
list = Object.new(connection: c, properties: c.get("/_api/web/lists/getbytitle('#{URI.encode CONFIG[:test_list_title]}')"))
|
27
|
+
expect(list['Title']).to eq(CONFIG[:test_list_title])
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can automatically get an object or collection" do
|
31
|
+
lists = c.get_as_object('/_api/web/lists/')
|
32
|
+
list = c.get_as_object("/_api/web/lists/getbytitle('#{URI.encode CONFIG[:test_list_title]}')")
|
33
|
+
expect(lists).to be_a(Collection)
|
34
|
+
expect(list).to be_a(Object)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can get a list by title" do
|
38
|
+
list = List.from_title(CONFIG[:test_list_title], c)
|
39
|
+
expect(list['Title']).to eq(CONFIG[:test_list_title])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can automatically retrieve deferred objects and c
|
43
|
+
ollections" do
|
44
|
+
list = List.from_title(CONFIG[:test_list_title], c)
|
45
|
+
expect(list['Items'][0]['ContentType']['Name']).to be_a(String)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "can give options when retrieving deferred objects" do
|
49
|
+
list = List.from_title(CONFIG[:test_list_title], c)
|
50
|
+
expect(list['Items'].to_a[0]['ContentType']['Name']).to be_nil
|
51
|
+
expect{list['Items', {expand: 'ContentType'}]}.to output.to_stderr # Should warn about deferred object already been fetched
|
52
|
+
list2 = List.from_title(CONFIG[:test_list_title], c)
|
53
|
+
expect(list2['Items', {expand: 'ContentType'}].to_a[0]['ContentType']['Name']).to be_a(String)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restful-sharepoint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Wardrop
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httpi
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- lib/restful-sharepoint/objects/web.rb
|
66
66
|
- lib/restful-sharepoint/version.rb
|
67
67
|
- restful-sharepoint.gemspec
|
68
|
+
- spec/restfulsharepoint_spec.rb
|
68
69
|
homepage: https://github.com/Wardrop/restful-sharepoint
|
69
70
|
licenses:
|
70
71
|
- MIT
|