occi-core 4.1.3 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +8 -0
  3. data/README.md +49 -17
  4. data/lib/occi/collection.rb +37 -21
  5. data/lib/occi/core/action.rb +5 -5
  6. data/lib/occi/core/action_instance.rb +45 -3
  7. data/lib/occi/core/actions.rb +2 -1
  8. data/lib/occi/core/attributes.rb +253 -73
  9. data/lib/occi/core/categories.rb +1 -0
  10. data/lib/occi/core/category.rb +25 -8
  11. data/lib/occi/core/entities.rb +1 -0
  12. data/lib/occi/core/entity.rb +51 -74
  13. data/lib/occi/core/kind.rb +15 -11
  14. data/lib/occi/core/kinds.rb +1 -1
  15. data/lib/occi/core/link.rb +14 -15
  16. data/lib/occi/core/links.rb +1 -1
  17. data/lib/occi/core/mixin.rb +5 -5
  18. data/lib/occi/core/mixins.rb +2 -2
  19. data/lib/occi/core/properties.rb +90 -12
  20. data/lib/occi/core/resource.rb +7 -3
  21. data/lib/occi/core/resources.rb +2 -2
  22. data/lib/occi/errors/attribute_definitions_converted_error.rb +5 -0
  23. data/lib/occi/errors/attribute_missing_error.rb +5 -0
  24. data/lib/occi/errors/attribute_name_invalid_error.rb +5 -0
  25. data/lib/occi/errors/attribute_not_defined_error.rb +5 -0
  26. data/lib/occi/errors/attribute_property_type_error.rb +5 -0
  27. data/lib/occi/errors/attribute_type_error.rb +5 -0
  28. data/lib/occi/errors/kind_not_defined_error.rb +5 -0
  29. data/lib/occi/errors/parser_input_error.rb +5 -0
  30. data/lib/occi/errors/parser_type_error.rb +5 -0
  31. data/lib/occi/errors.rb +1 -0
  32. data/lib/occi/extensions/hashie.rb +25 -0
  33. data/lib/occi/helpers/comparators/action_instance.rb +22 -0
  34. data/lib/occi/helpers/comparators/attributes.rb +22 -0
  35. data/lib/occi/helpers/comparators/categories.rb +22 -0
  36. data/lib/occi/helpers/comparators/category.rb +22 -0
  37. data/lib/occi/helpers/comparators/collection.rb +40 -0
  38. data/lib/occi/helpers/comparators/entities.rb +22 -0
  39. data/lib/occi/helpers/comparators/entity.rb +22 -0
  40. data/lib/occi/helpers/comparators/properties.rb +26 -0
  41. data/lib/occi/helpers/comparators.rb +1 -0
  42. data/lib/occi/infrastructure/compute.rb +11 -9
  43. data/lib/occi/infrastructure/network.rb +27 -27
  44. data/lib/occi/infrastructure/networkinterface.rb +22 -23
  45. data/lib/occi/infrastructure/os_tpl.rb +1 -1
  46. data/lib/occi/infrastructure/resource_tpl.rb +1 -1
  47. data/lib/occi/infrastructure/storage.rb +7 -6
  48. data/lib/occi/infrastructure/storagelink.rb +4 -4
  49. data/lib/occi/log.rb +13 -10
  50. data/lib/occi/model.rb +9 -8
  51. data/lib/occi/parser/json.rb +11 -9
  52. data/lib/occi/parser/ova.rb +12 -6
  53. data/lib/occi/parser/ovf.rb +173 -116
  54. data/lib/occi/parser/text/constants.rb +87 -0
  55. data/lib/occi/parser/text.rb +161 -200
  56. data/lib/occi/parser/xml.rb +10 -8
  57. data/lib/occi/parser.rb +100 -50
  58. data/lib/occi/settings.rb +2 -1
  59. data/lib/occi/version.rb +1 -1
  60. data/lib/occi-core.rb +6 -4
  61. data/occi-core.gemspec +0 -7
  62. data/spec/occi/collection_samples/collection1.json +1 -0
  63. data/spec/occi/collection_samples/directory2/collection2.json +1 -0
  64. data/spec/occi/collection_spec.rb +961 -31
  65. data/spec/occi/core/action_instance_spec.rb +317 -0
  66. data/spec/occi/core/action_spec.rb +71 -0
  67. data/spec/occi/core/attributes_spec.rb +582 -27
  68. data/spec/occi/core/category_spec.rb +194 -18
  69. data/spec/occi/core/entities_spec.rb +96 -0
  70. data/spec/occi/core/entity_spec.rb +317 -28
  71. data/spec/occi/core/kind_spec.rb +127 -16
  72. data/spec/occi/core/link_spec.rb +35 -0
  73. data/spec/occi/core/links_spec.rb +130 -0
  74. data/spec/occi/core/mixins_spec.rb +107 -0
  75. data/spec/occi/core/properties_spec.rb +167 -0
  76. data/spec/occi/core/resource_spec.rb +23 -9
  77. data/spec/occi/core_spec.rb +12 -0
  78. data/spec/occi/infrastructure/compute_spec.rb +218 -18
  79. data/spec/occi/infrastructure/network_spec.rb +96 -0
  80. data/spec/occi/infrastructure/networkinterface_spec.rb +96 -0
  81. data/spec/occi/infrastructure/storage_spec.rb +33 -0
  82. data/spec/occi/infrastructure/storagelink_spec.rb +45 -0
  83. data/spec/occi/log_spec.rb +104 -1
  84. data/spec/occi/model_spec.rb +251 -39
  85. data/spec/occi/{test.json → parser/json_samples/test.json} +0 -0
  86. data/spec/occi/parser/ova_samples/test.dump +0 -0
  87. data/spec/occi/{test.ova → parser/ova_samples/test.ova} +0 -0
  88. data/spec/occi/parser/ovf_samples/test.dump +0 -0
  89. data/spec/occi/{test.ovf → parser/ovf_samples/test.ovf} +0 -0
  90. data/spec/occi/parser/text_samples/occi_categories.dump +0 -0
  91. data/spec/occi/parser/text_samples/occi_categories.text +2 -0
  92. data/spec/occi/parser/text_samples/occi_compute_rocci_server.dump +0 -0
  93. data/spec/occi/parser/text_samples/occi_compute_rocci_server.resource.dump +0 -0
  94. data/spec/occi/parser/text_samples/occi_compute_rocci_server.text +10 -0
  95. data/spec/occi/parser/text_samples/occi_link_resource_instance.dump +0 -0
  96. data/spec/occi/parser/text_samples/occi_link_resource_instance.text +7 -0
  97. data/spec/occi/parser/text_samples/occi_link_simple.dump +0 -0
  98. data/spec/occi/parser/text_samples/occi_link_simple.link_string.dump +0 -0
  99. data/spec/occi/parser/text_samples/occi_link_simple.text +1 -0
  100. data/spec/occi/parser/text_samples/occi_link_w_attributes.dump +0 -0
  101. data/spec/occi/parser/text_samples/occi_link_w_attributes.text +7 -0
  102. data/spec/occi/parser/text_samples/occi_link_w_category.dump +0 -0
  103. data/spec/occi/parser/text_samples/occi_link_w_category.text +3 -0
  104. data/spec/occi/parser/text_samples/occi_model_rocci_server.dump +0 -0
  105. data/spec/occi/parser/text_samples/occi_model_rocci_server.text +51 -0
  106. data/spec/occi/parser/text_samples/occi_network_rocci_server.dump +0 -0
  107. data/spec/occi/parser/text_samples/occi_network_rocci_server.resource.dump +0 -0
  108. data/spec/occi/parser/text_samples/occi_network_rocci_server.text +11 -0
  109. data/spec/occi/parser/text_samples/occi_resource_w_attributes.dump +0 -0
  110. data/spec/occi/parser/text_samples/occi_resource_w_attributes.text +11 -0
  111. data/spec/occi/parser/text_samples/occi_resource_w_inline_links.dump +0 -0
  112. data/spec/occi/parser/text_samples/occi_resource_w_inline_links.text +16 -0
  113. data/spec/occi/parser/text_samples/occi_resource_w_inline_links_only.dump +0 -0
  114. data/spec/occi/parser/text_samples/occi_resource_w_inline_links_only.text +13 -0
  115. data/spec/occi/parser/text_samples/occi_storage_rocci_server.dump +0 -0
  116. data/spec/occi/parser/text_samples/occi_storage_rocci_server.resource.dump +0 -0
  117. data/spec/occi/parser/text_samples/occi_storage_rocci_server.text +9 -0
  118. data/spec/occi/parser/text_spec.rb +274 -78
  119. data/spec/occi/parser/xml_samples/test.xml +352 -0
  120. data/spec/occi/parser_spec.rb +255 -104
  121. data/spec/occi-core_spec.rb +31 -0
  122. data/spec/spec_helper.rb +6 -2
  123. metadata +110 -111
  124. checksums.yaml +0 -7
  125. data/spec/occi/core/attribute_spec.rb +0 -0
data/.gitignore CHANGED
@@ -13,3 +13,4 @@ coverage
13
13
  vendor
14
14
  collection
15
15
  Gemfile.lock
16
+ doc
data/Gemfile CHANGED
@@ -4,4 +4,12 @@ gemspec
4
4
 
5
5
  group :development do
6
6
  gem 'rubygems-tasks', :git => 'git://github.com/postmodern/rubygems-tasks.git'
7
+ gem 'json_spec', :git => "https://github.com/collectiveidea/json_spec", :branch => "master"
8
+ gem 'rspec'
9
+ gem 'rake'
10
+ gem 'builder'
11
+ gem 'simplecov'
12
+ gem 'yard'
13
+ gem 'yard-rspec'
14
+ gem 'debugger', :platforms => :ruby
7
15
  end
data/README.md CHANGED
@@ -14,20 +14,30 @@ Requirements
14
14
  * RubyGems have to be installed
15
15
  * Rake has to be installed (e.g., `gem install rake`)
16
16
 
17
- ### Libraries/packages
18
- * libxslt1-dev/libxslt-devel
19
- * libxml2-dev/libxml2-devel
17
+ ### Dependencies
18
+ * `libxslt1-dev` or `libxslt-devel`
19
+ * `libxml2-dev`or `libxml2-devel`
20
20
 
21
21
  ### Examples
22
- For distros based on Debian:
22
+ #### For distros based on Debian:
23
23
  ~~~
24
24
  apt-get install ruby rubygems ruby-dev libxslt1-dev libxml2-dev
25
25
  ~~~
26
+ ~~~
27
+ ruby -v
28
+ ~~~
29
+
30
+ **Unless you have Ruby >= 1.9.3, please, go to [rOCCI-core#RVM](#rvm) and install RVM with a newer Ruby version.**
26
31
 
27
- For distros based on RHEL:
32
+ #### For distros based on RHEL:
28
33
  ~~~
29
34
  yum install libxml2-devel libxslt-devel ruby-devel openssl-devel gcc gcc-c++ ruby rubygems
30
35
  ~~~
36
+ ~~~
37
+ ruby -v
38
+ ~~~
39
+
40
+ **Unless you have Ruby >= 1.9.3, please, go to [rOCCI-core#RVM](#rvm) and install RVM with a newer Ruby version.**
31
41
 
32
42
  Installation
33
43
  ------------
@@ -47,12 +57,8 @@ To install the most recent beta version
47
57
  ### From source (dev)
48
58
 
49
59
  **Installation from source should never be your first choice! Especially, if you are not familiar with RVM, Bundler, Rake and other dev tools for Ruby!**
50
- **However, if you wish to contribute to our project, this is the right way to start.**
51
-
52
- To use rOCCI from source it is very much recommended to use RVM. [Install RVM](https://rvm.io/rvm/install/) with
53
60
 
54
- curl -L https://get.rvm.io | bash -s stable --ruby
55
- rvm install 1.9.3
61
+ **However, if you wish to contribute to our project, this is the right way to start.**
56
62
 
57
63
  To build and install the bleeding edge version from master
58
64
 
@@ -63,9 +69,29 @@ To build and install the bleeding edge version from master
63
69
  bundle exec rake test
64
70
  rake install
65
71
 
72
+ ### RVM
73
+
74
+ **Notice:** Follow the RVM installation guide linked below, we recommend using the default 'Single-User installation'.
75
+
76
+ **Warning:** NEVER install RVM as root! If you choose the 'Multi-User installation', use a different user account with sudo access instead!
77
+
78
+ * [Installing RVM](https://rvm.io/rvm/install#explained)
79
+ * Install Ruby
80
+
81
+ ~~~
82
+ rvm requirements
83
+ rvm install 1.9.3
84
+ rvm use 1.9.3 --default
85
+ ~~~
86
+ ~~~
87
+ ruby -v
88
+ ~~~
89
+
66
90
  Usage
67
91
  -----
68
- #### Logging
92
+ Detailed documentation is available in our [Wiki](https://github.com/gwdg/rOCCI-core/wiki).
93
+
94
+ ### Logging
69
95
 
70
96
  The OCCI gem includes its own logging mechanism using a message queue. By default, no one is listening to that queue.
71
97
  A new OCCI Logger can be initialized by specifying the log destination (either a filename or an IO object like
@@ -80,7 +106,7 @@ You can always, even if there is no logger defined, log output using the class m
80
106
 
81
107
  Occi::Log.info("Test message")
82
108
 
83
- #### Registering categories in the OCCI Model
109
+ ### Registering categories in the OCCI Model
84
110
 
85
111
  Before the parser may be used, the available categories have to be registered in the OCCI Model.
86
112
 
@@ -89,16 +115,16 @@ For categories already specified by the OCCI WG a method exists in the OCCI Mode
89
115
  model = Occi::Model.new
90
116
  model.register_infrastructure
91
117
 
92
- Further categories can either be registered from files which include OCCI collections in JSON formator or from parsed
118
+ Further categories can either be registered from files which include OCCI collections in JSON format or or from parsed
93
119
  JSON objects (e.g. from the query interface of an OCCI service endpoint).
94
120
 
95
- #### Parsing OCCI messages
121
+ ### Parsing OCCI messages
96
122
 
97
123
  The OCCI gem includes a Parser to easily parse OCCI messages. With a given media type (e.g. json,
98
124
  xml or plain text) the parser analyses the content of the message body and, if supplied,
99
125
  the message header. As the text/plain and text/occi media type do not clearly distinguish between a message with a
100
- category and a message with an entity which has a kind, it has to be specified if the message contains a category (e
101
- .g. for user defined mixins)
126
+ category and a message with an entity which has a kind, it has to be specified if the message contains a category
127
+ (e.g. for user defined mixins)
102
128
 
103
129
  OCCI messages can be parsed to an OCCI collection for example like
104
130
 
@@ -106,7 +132,7 @@ OCCI messages can be parsed to an OCCI collection for example like
106
132
  body = %Q|Category: compute; scheme="http://schemas.ogf.org/occi/infrastructure#"; class="kind"|
107
133
  collection=Occi::Parser.parse(media_type, body)
108
134
 
109
- #### Parsing OVF / OVA files
135
+ ### Parsing OVF / OVA files
110
136
 
111
137
  Parsing of OVF/OVA files is partly supported and will be improved in future versions.
112
138
 
@@ -137,6 +163,12 @@ The occi-core gem includes all OCCI Core classes necessary to handly arbitrary O
137
163
  Changelog
138
164
  ---------
139
165
 
166
+ ### Version 4.2
167
+ * Internal changes and bug fixes
168
+ * Extended test coverage
169
+ * Added custom exceptions and error classes
170
+ * Improved text/plain and text/occi rendering
171
+
140
172
  ### Version 4.1
141
173
  * Dropped support for Rubies 1.8.x
142
174
  * Updated dependencies
@@ -2,6 +2,7 @@ module Occi
2
2
  class Collection
3
3
 
4
4
  include Occi::Helpers::Inspect
5
+ include Occi::Helpers::Comparators::Collection
5
6
 
6
7
  attr_accessor :kinds, :mixins, :actions, :resources, :links, :action, :model
7
8
 
@@ -24,19 +25,17 @@ module Occi
24
25
  @actions.merge collection.actions.to_a.collect { |action| Occi::Core::Action.new(action.scheme, action.term, action.title, action.attributes) }
25
26
  @resources.merge collection.resources.to_a.collect { |resource| Occi::Core::Resource.new(resource.kind, resource.mixins, resource.attributes, resource.links) }
26
27
  @links.merge collection.links.to_a.collect { |link| Occi::Core::Link.new(link.kind, link.mixins, link.attributes) }
27
- @action = Occi::Core::ActionInstance.new(collection.action, collection.attributes) if collection.action
28
+ @action = Occi::Core::ActionInstance.new(collection.action) if collection.action
28
29
  end
29
30
 
30
31
  def <<(object)
31
- object.kind_of? Occi::Core::Kind and self.kinds << object
32
- object.kind_of? Occi::Core::Mixin and self.mixins << object
33
- object.kind_of? Occi::Core::Action and self.actions << object
34
- object.kind_of? Occi::Core::Resource and self.resources << object
35
- object.kind_of? Occi::Core::Link and self.links << object
36
- end
32
+ self.kinds << object if object.kind_of? Occi::Core::Kind
33
+ self.mixins << object if object.kind_of? Occi::Core::Mixin
34
+ self.actions << object if object.kind_of? Occi::Core::Action
35
+ self.resources << object if object.kind_of? Occi::Core::Resource
36
+ self.links << object if object.kind_of? Occi::Core::Link
37
37
 
38
- def ==(category)
39
- not intersect(category).empty?
38
+ self
40
39
  end
41
40
 
42
41
  # @return [Occi::Core::Categories] categories combined list of all kinds, mixins and actions
@@ -70,19 +69,23 @@ module Occi
70
69
  # @param [Occi::Collection] other_collection
71
70
  # @return [Occi::Collection]
72
71
  def merge!(other_collection)
73
- merge other_collection, self
72
+ self.kinds.merge other_collection.kinds.select { |kind| get_by_id(kind.type_identifier).nil? }
73
+ self.mixins.merge other_collection.mixins.select { |mixin| get_by_id(mixin.type_identifier).nil? }
74
+ self.actions.merge other_collection.actions.select { |action| get_by_id(action.type_identifier).nil? }
75
+ self.resources.merge other_collection.resources.select { |resource| get_by_id(resource.id).nil? }
76
+ self.links.merge other_collection.links.select { |link| get_by_id(link.id).nil? }
77
+ self.action = other_collection.action if other_collection.action
74
78
  end
75
79
 
76
80
  # @param [Occi::Collection] other_collection
77
81
  # @param [Occi::Collection] collection
78
82
  # @return [Occi::Collection]
79
- def merge(other_collection, collection=self.clone)
80
- collection.kinds.merge other_collection.kinds.select { |kind| get_by_id(kind.type_identifier).nil? }
81
- collection.mixins.merge other_collection.mixins.select { |mixin| get_by_id(mixin.type_identifier).nil? }
82
- collection.actions.merge other_collection.actions.select { |action| get_by_id(action.type_identifier).nil? }
83
- collection.resources.merge other_collection.resources.select { |resource| get_by_id(resource.id).nil? }
84
- collection.links.merge other_collection.links.select { |link| get_by_id(link.type_identifier).nil? }
85
- collection.action = other_collection.action if other_collection.action
83
+ def merge(other_collection, first=self)
84
+ collection = Occi::Collection.new
85
+
86
+ collection.merge!(first)
87
+ collection.merge!(other_collection)
88
+
86
89
  collection
87
90
  end
88
91
 
@@ -100,7 +103,7 @@ module Occi
100
103
  collection.mixins.replace other_collection.mixins.select { |mixin| get_by_id(mixin.type_identifier) }
101
104
  collection.actions.replace other_collection.actions.select { |action| get_by_id(action.type_identifier) }
102
105
  collection.resources.replace other_collection.resources.select { |resource| get_by_id(resource.id) }
103
- collection.links.replace other_collection.links.select { |link| get_by_id(link.type_identifier) }
106
+ collection.links.replace other_collection.links.select { |link| get_by_id(link.id) }
104
107
  if collection.action == other_collection.action
105
108
  collection.action = other_collection.action
106
109
  else
@@ -182,11 +185,24 @@ module Occi
182
185
  header = Hashie::Mash.new
183
186
  header['Category'] = self.categories.collect { |category| category.to_string_short }.join(',') if self.categories.any?
184
187
  raise "Only one resource allowed for rendering to text/occi" if self.resources.size > 1
185
- header = self.resources.first.to_header if self.resources.any?
188
+ header = self.class.header_merge(header, self.resources.first.to_header) if self.resources.any?
186
189
  header['Link'] = self.links.collect { |link| link.to_string }.join(',') if self.links.any?
187
- header = self.action.to_header if self.action
190
+ header = self.class.header_merge(header, self.action.to_header) if self.action
188
191
  header
189
192
  end
190
193
 
194
+ private
195
+
196
+ def self.header_merge(target, other, separator=',')
197
+ other.each_pair do |key,val|
198
+ if target.key?(key)
199
+ target[key] = "#{target[key]}#{separator}#{val}"
200
+ else
201
+ target[key] = val
202
+ end
203
+ end
204
+ target
205
+ end
206
+
191
207
  end
192
- end
208
+ end
@@ -2,7 +2,7 @@ module Occi
2
2
  module Core
3
3
  class Action < Occi::Core::Category
4
4
 
5
- # @param [String ] scheme
5
+ # @param [String] scheme
6
6
  # @param [String] term
7
7
  # @param [String] title
8
8
  # @param [Hash] attributes
@@ -10,23 +10,23 @@ module Occi
10
10
  term='action',
11
11
  title=nil,
12
12
  attributes=Occi::Core::Attributes.new)
13
- super scheme, term, title, attributes
13
+ super(scheme, term, title, attributes)
14
14
  end
15
15
 
16
16
  # @return [String] text representation
17
17
  def to_text
18
18
  text = super
19
- text << ';attributes=' + @attributes.names.join(' ').inspect if @attributes.any?
19
+ text << "#{@attributes.to_string_short}"
20
20
  text
21
21
  end
22
22
 
23
23
  # @return [Hash] hash containing the HTTP headers of the text/occi rendering
24
24
  def to_header
25
25
  header = super
26
- header["Category"] << ';attributes=' + @attributes.names.join(' ').inspect if @attributes.any?
26
+ header[:Category] << "#{@attributes.to_string_short}"
27
27
  header
28
28
  end
29
29
 
30
30
  end
31
31
  end
32
- end
32
+ end
@@ -3,6 +3,7 @@ module Occi
3
3
  class ActionInstance
4
4
 
5
5
  include Occi::Helpers::Inspect
6
+ include Occi::Helpers::Comparators::ActionInstance
6
7
 
7
8
  attr_accessor :action, :attributes, :model
8
9
 
@@ -16,12 +17,17 @@ module Occi
16
17
  attributes=self.attributes
17
18
 
18
19
  def initialize(action = self.action, attributes=self.attributes)
20
+ raise ArgumentError, 'action cannot be nil' unless action
21
+ raise ArgumentError, 'attributes cannot be nil' unless attributes
22
+ raise ArgumentError, 'attributes must respond to #convert' unless attributes.respond_to? :convert
23
+
19
24
  if action.kind_of? String
20
25
  scheme, term = action.split '#'
21
26
  action = Occi::Core::Action.new(scheme, term)
22
27
  end
28
+
23
29
  @action = action
24
- @attributes = attributes.convert
30
+ @attributes = Occi::Core::Attributes.new(attributes)
25
31
  end
26
32
 
27
33
  # @param [Hash] options
@@ -29,10 +35,46 @@ module Occi
29
35
  def as_json(options={})
30
36
  action = Hashie::Mash.new
31
37
  action.action = @action.to_s if @action
32
- action.attributes = @attributes if @attributes.any?
38
+ action.attributes = @attributes.any? ? @attributes.as_json : Occi::Core::Attributes.new.as_json
33
39
  action
34
40
  end
35
41
 
42
+ # @return [String] text representation
43
+ def to_text
44
+ text = "Category: #{@action.to_string_short}"
45
+ @attributes.names.each_pair do |name, value|
46
+ value = value.to_s.inspect unless value && value.is_a?(Numeric)
47
+ text << "\nX-OCCI-Attribute: #{name}=#{value}"
48
+ end
49
+
50
+ text
51
+ end
52
+
53
+ # @return [String] JSON representation
54
+ def to_json
55
+ as_json.to_json
56
+ end
57
+
58
+ # @return [Hash] hash containing the HTTP headers of the text/occi rendering
59
+ def to_header
60
+ header = Hashie::Mash.new
61
+ header['Category'] = @action.to_string_short
62
+
63
+ attributes = []
64
+ @attributes.names.each_pair do |name, value|
65
+ value = value.to_s.inspect unless value && value.is_a?(Numeric)
66
+ attributes << "#{name}=#{value}"
67
+ end
68
+ header['X-OCCI-Attribute'] = attributes.join(',') if attributes.any?
69
+
70
+ header
71
+ end
72
+
73
+ # @return [Bool] Indicating whether this action instance is "empty", i.e. required attributes are blank
74
+ def empty?
75
+ action.nil? || action.empty?
76
+ end
77
+
36
78
  end
37
79
  end
38
- end
80
+ end
@@ -9,9 +9,10 @@ module Occi
9
9
 
10
10
  if category.kind_of? String
11
11
  scheme, term = category.split '#'
12
- scheme += '#'
12
+ scheme << '#'
13
13
  category = Occi::Core::Action.new(scheme, term)
14
14
  end
15
+
15
16
  category
16
17
  end
17
18