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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed833ac3ebadfee98a8b6fa2b9da7cdc28b52123328ebd90ab1ae997850e2461
4
- data.tar.gz: 546418c16f83e2072d0cdd36eade766b99cea7450e1b93533768493f8b8b003c
3
+ metadata.gz: 676e57e07950be0b111e91a9675056ab7f9b3d90c39319dc1e9ac619476e7cec
4
+ data.tar.gz: 717a90610e1a58b0895eed3fe018f3d99faae5f7947ce1b81797c0e8cf9fe6c1
5
5
  SHA512:
6
- metadata.gz: 41d9bd7486df21258be7ef1ffa903607128e303d7f2245d3dc5ec9ed94a44fbc52ee8c5e927380d3fc51fcb70b53f05fe9a393ec504d08acb4d5ebc9d024129f
7
- data.tar.gz: 93134acddffbafb778f7254c955ccc61c2e38818d66d460e065a92ac215d6f44e4759c84f9cd4d33fc9e5289edb2c7cd8c1553ae4144c8867602637cee112c8f
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 = connection || @parent.connection # Iterate collection and coerce each into into a ListItem
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 needs to be set")
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
- @properties
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 values
41
- collection.dup.each { |k,v| properties[k] = v.values if v.is_a?(Object) || v.is_a?(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
- # Converts the given enumerable tree to a collection or object.
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.to_json.gsub('/', '\\/') # SharePoint requires forward slashes be escaped in JSON (WTF!!!)
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
- data_tree = parse(response.body)
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.parse(str)
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 = connection || @parent.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 properties=(properties)
26
- @properties = properties
27
- @properties&.each do |k,v|
28
- if v.respond_to?(:keys) && v['__deferred']
29
- define_singleton_method(k) do |options = {}|
30
- if Hash === properties[k] && properties[k]['__deferred']
31
- fetch_deferred(k, options)
32
- else
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 properties
45
- @properties || self.properties = connection.get(endpoint, options: @options)
46
+ def ==(other)
47
+ other.== properties
48
+ end
49
+
50
+ def eql?(other)
51
+ other.eql? properties
46
52
  end
47
53
 
48
- def values
49
- properties.dup.each { |k,v| properties[k] = v.values if v.is_a?(Object) || v.is_a?(Collection) }
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
- data = connection.get(@properties[property]['__deferred']['uri'], options: options)
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 method_missing(method, *args, &block)
62
- if properties.respond_to?(method)
63
- properties.send(method, *args, &block)
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
@@ -1,3 +1,3 @@
1
1
  module RestfulSharePoint
2
- VERSION = '0.1.7'
2
+ VERSION = '0.2.2'
3
3
  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.1.7
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-04-04 00:00:00.000000000 Z
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