restful-sharepoint 0.1.7 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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