oscal 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.docker/Dockerfile +19 -0
  3. data/.docker/Makefile +43 -0
  4. data/.docker/docker-compose.yml +14 -0
  5. data/.docker/readme.md +61 -0
  6. data/.github/workflows/rake.yml +15 -0
  7. data/.github/workflows/release.yml +24 -0
  8. data/.gitignore +13 -0
  9. data/.gitmodules +3 -0
  10. data/.hound.yml +5 -0
  11. data/.rspec +0 -1
  12. data/.rubocop.yml +10 -0
  13. data/.ruby-version +1 -0
  14. data/Gemfile +4 -0
  15. data/LICENSE +25 -0
  16. data/Makefile +1 -0
  17. data/README.adoc +66 -0
  18. data/Rakefile +13 -6
  19. data/bin/console +30 -0
  20. data/bin/rspec +27 -0
  21. data/bin/setup +8 -0
  22. data/docker-compose.yml +1 -0
  23. data/exe/convert2oscalyaml.rb +560 -0
  24. data/lib/oscal/add.rb +26 -0
  25. data/lib/oscal/address.rb +22 -0
  26. data/lib/oscal/address_line.rb +11 -0
  27. data/lib/oscal/alter.rb +22 -0
  28. data/lib/oscal/assembly.rb +119 -0
  29. data/lib/oscal/assessment_plan.rb +28 -0
  30. data/lib/oscal/assessment_result.rb +230 -0
  31. data/lib/oscal/attribute_type_hash.rb +80 -0
  32. data/lib/oscal/back_matter.rb +20 -0
  33. data/lib/oscal/base64_object.rb +11 -0
  34. data/lib/oscal/base_class.rb +50 -0
  35. data/lib/oscal/catalog.rb +51 -10
  36. data/lib/oscal/choice.rb +11 -0
  37. data/lib/oscal/citation.rb +22 -0
  38. data/lib/oscal/combine.rb +11 -0
  39. data/lib/oscal/common_utils.rb +35 -0
  40. data/lib/oscal/constraint.rb +20 -0
  41. data/lib/oscal/control.rb +20 -30
  42. data/lib/oscal/custom.rb +22 -0
  43. data/lib/oscal/datatypes.rb +50 -0
  44. data/lib/oscal/document_id.rb +11 -0
  45. data/lib/oscal/email_address.rb +11 -0
  46. data/lib/oscal/exclude_control.rb +22 -0
  47. data/lib/oscal/external_id.rb +11 -0
  48. data/lib/oscal/group.rb +26 -34
  49. data/lib/oscal/guideline.rb +11 -0
  50. data/lib/oscal/hash_object.rb +11 -0
  51. data/lib/oscal/import_object.rb +22 -0
  52. data/lib/oscal/include_control.rb +22 -0
  53. data/lib/oscal/insert_control.rb +22 -0
  54. data/lib/oscal/link.rb +11 -0
  55. data/lib/oscal/list.rb +160 -0
  56. data/lib/oscal/location.rb +31 -0
  57. data/lib/oscal/location_uuid.rb +11 -0
  58. data/lib/oscal/logger.rb +8 -0
  59. data/lib/oscal/matching.rb +11 -0
  60. data/lib/oscal/member_of_organization.rb +11 -0
  61. data/lib/oscal/merge.rb +20 -0
  62. data/lib/oscal/metadata_block.rb +28 -13
  63. data/lib/oscal/modify.rb +22 -0
  64. data/lib/oscal/parameter.rb +22 -19
  65. data/lib/oscal/parsing_functions.rb +19 -0
  66. data/lib/oscal/part.rb +14 -22
  67. data/lib/oscal/party.rb +36 -0
  68. data/lib/oscal/party_uuid.rb +11 -0
  69. data/lib/oscal/profile.rb +33 -7
  70. data/lib/oscal/property.rb +4 -25
  71. data/lib/oscal/remove.rb +11 -0
  72. data/lib/oscal/resource.rb +29 -0
  73. data/lib/oscal/responsible_party.rb +24 -0
  74. data/lib/oscal/revision.rb +23 -0
  75. data/lib/oscal/rlink.rb +20 -0
  76. data/lib/oscal/role.rb +22 -0
  77. data/lib/oscal/select.rb +20 -0
  78. data/lib/oscal/serializer.rb +17 -4
  79. data/lib/oscal/set_parameter.rb +31 -0
  80. data/lib/oscal/telephone_number.rb +11 -0
  81. data/lib/oscal/test.rb +11 -0
  82. data/lib/oscal/url.rb +11 -0
  83. data/lib/oscal/value.rb +37 -0
  84. data/lib/oscal/version.rb +1 -1
  85. data/lib/oscal/with_id.rb +40 -0
  86. data/lib/oscal.rb +1 -13
  87. data/oscal.gemspec +9 -11
  88. data/spec/oscal/catalog_spec.rb +40 -0
  89. data/spec/oscal_spec.rb +7 -0
  90. data/spec/sample_inputs/import-ap.json +4 -0
  91. data/spec/spec_helper.rb +15 -0
  92. metadata +84 -10
  93. data/lib/oscal/component.rb +0 -14
  94. data/lib/oscal/prose.rb +0 -13
  95. data/lib/oscal/statement.rb +0 -12
@@ -1,21 +1,36 @@
1
- module Oscal
2
- class MetadataBlock
1
+ require_relative "base_class"
3
2
 
4
- METADATA_VALUES = %i(title published last_modified version
5
- oscal_version remarks)
3
+ module Oscal
4
+ class MetadataBlock < Oscal::BaseClass
5
+ KEY = %i(title published last_modified version
6
+ oscal_version revisions document_ids props links roles
7
+ locations parties responsible_parties remarks)
6
8
 
7
- attr_accessor *METADATA_VALUES
9
+ attr_accessor *KEY
8
10
 
9
- def initialize(options={})
10
- options.each_pair.each do |key,val|
11
- key_name = key.gsub('-','_')
11
+ attr_serializable *KEY
12
12
 
13
- unless METADATA_VALUES.include?(key_name.to_sym)
14
- raise UnknownAttributeError.new("Unknown key `#{key}` in MetadataBlock")
15
- end
16
- self.send("#{key_name}=", val)
13
+ def set_value(key_name, val)
14
+ case key_name
15
+ when "revisions"
16
+ Revision.wrap(val)
17
+ when "docuement_ids"
18
+ DocumentId.wrap(val)
19
+ when "props"
20
+ Property.wrap(val)
21
+ when "links"
22
+ Link.wrap(val)
23
+ when "roles"
24
+ Role.wrap(val)
25
+ when "locations"
26
+ Location.wrap(val)
27
+ when "parties"
28
+ Party.wrap(val)
29
+ when "responsible_parties"
30
+ ResponsibleParty.wrap(val)
31
+ else
32
+ val
17
33
  end
18
34
  end
19
-
20
35
  end
21
36
  end
@@ -0,0 +1,22 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Modify < Oscal::BaseClass
5
+ KEY = %i(set_parameters alters)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "set_parameters"
14
+ SetParameter.wrap(val)
15
+ when "alters"
16
+ Alter.wrap(val)
17
+ else
18
+ val
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,27 +1,30 @@
1
+ require_relative "base_class"
2
+
1
3
  module Oscal
2
- class Parameter
4
+ class Parameter < Oscal::BaseClass
5
+ KEY = %i(id klass depneds_on props links label usage
6
+ constraints guidelines values select remarks)
3
7
 
4
- KEY = %i(id label select)
5
8
  attr_accessor *KEY
6
9
 
7
- def self.wrap(obj)
8
- return obj if obj.is_a? Parameter
9
- return Parameter.new(obj) unless obj.is_a? Array
10
-
11
- obj.map do |x|
12
- Parameter.wrap(x)
13
- end
14
- end
15
-
16
- def initialize(options={})
17
- options.each_pair.each do |key,val|
18
- key_name = key.gsub('-','_')
19
-
20
- unless KEY.include?(key_name.to_sym)
21
- raise UnknownAttributeError.new("Unknown key `#{key}` in Parameter")
22
- end
10
+ attr_serializable *KEY
23
11
 
24
- self.send("#{key_name}=", val)
12
+ def set_value(key_name, val)
13
+ case key_name
14
+ when "props"
15
+ Property.wrap(val)
16
+ when "links"
17
+ Link.wrap(val)
18
+ when "constraints"
19
+ Constraint.wrap(val)
20
+ when "guidelines"
21
+ Guideline.wrap(val)
22
+ when "values"
23
+ Value.wrap(val)
24
+ when "select"
25
+ Select.wrap(val)
26
+ else
27
+ val
25
28
  end
26
29
  end
27
30
  end
@@ -0,0 +1,19 @@
1
+ module Oscal
2
+ module ParsingFunctions
3
+ def sym2str(key)
4
+ if key.is_a?(Symbol)
5
+ key.to_s.gsub("-", "_").gsub("class", "klass")
6
+ elsif key.is_a?(String)
7
+ key
8
+ end
9
+ end
10
+
11
+ def str2sym(var)
12
+ if var.is_a?(String)
13
+ var.gsub("-", "_").gsub("class", "klass").to_sym
14
+ elsif var.is_a?(Symbol)
15
+ var
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/oscal/part.rb CHANGED
@@ -1,31 +1,23 @@
1
- require_relative "serializer"
1
+ require_relative "base_class"
2
2
 
3
3
  module Oscal
4
- class Part
5
- include Serializer
4
+ class Part < Oscal::BaseClass
5
+ KEY = %i(id name ns klass title props prose parts links)
6
6
 
7
- KEY = %i(id name prose)
8
7
  attr_accessor *KEY
9
- attr_serializable *KEY
10
-
11
- def self.wrap(obj)
12
- return obj if obj.is_a? Part
13
- return Part.new(obj) unless obj.is_a? Array
14
8
 
15
- obj.map do |x|
16
- Part.wrap(x)
17
- end
18
- end
19
-
20
- def initialize(options={})
21
- options.each_pair.each do |key,val|
22
- key_name = key.gsub('-','_')
23
-
24
- unless KEY.include?(key_name.to_sym)
25
- raise UnknownAttributeError.new("Unknown key `#{key}` in Part")
26
- end
9
+ attr_serializable *KEY
27
10
 
28
- self.send("#{key_name}=", val)
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "props"
14
+ Property.wrap(val)
15
+ when "parts"
16
+ Part.wrap(val)
17
+ when "links"
18
+ Link.wrap(val)
19
+ else
20
+ val
29
21
  end
30
22
  end
31
23
  end
@@ -0,0 +1,36 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Party < Oscal::BaseClass
5
+ KEY = %i(uuid type name short_name external_ids props links
6
+ email_addresses telephone_numbers addresses location_uuids
7
+ member_of_organizations remakrs)
8
+
9
+ attr_accessor *KEY
10
+
11
+ attr_serializable *KEY
12
+
13
+ def set_value(key_name, val)
14
+ case key_name
15
+ when "external_ids"
16
+ ExternalId.wrap(val)
17
+ when "props"
18
+ Property.wrap(val)
19
+ when "links"
20
+ Link.wrap(val)
21
+ when "email_addresses"
22
+ EmailAddress.wrap(val)
23
+ when "telephone_numbers"
24
+ TelephoneNumber.wrap(val)
25
+ when "addresses"
26
+ Address.wrap(val)
27
+ when "location_uuids"
28
+ LocationUuid.wrap(val)
29
+ when "member_of_organizations"
30
+ MemberOfOrganization.wrap(val)
31
+ else
32
+ val
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class PartyUuid < Oscal::BaseClass
5
+ KEY = %i(val)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
data/lib/oscal/profile.rb CHANGED
@@ -1,11 +1,37 @@
1
+ require_relative "serializer"
2
+ require_relative "common_utils"
3
+
1
4
  module Oscal
2
- class Profile
3
- attr_accessor :metadata, :imports, :controls
5
+ class Profile
6
+ include Serializer
7
+ include CommonUtils
8
+
9
+ KEY = %i(uuid metadata imports merge modify back_matter)
10
+ attr_accessor *KEY
11
+
12
+ attr_serializable *KEY
13
+
14
+ def initialize(uuid, metadata, imports, merge, modify, back_matter)
15
+ @uuid = uuid
16
+ @metadata = MetadataBlock.new(metadata)
17
+ @imports = ImportObject.wrap(imports) if imports
18
+ @merge = Merge.wrap(merge) if merge
19
+ @modify = Modify.wrap(modify) if modify
20
+ @back_matter = BackMatter.wrap(back_matter) if back_matter
21
+ end
22
+
23
+ def self.load_from_yaml(path)
24
+ yaml_data = safe_load_yaml(path)
25
+ yaml_profile = yaml_data["profile"]
26
+
27
+ uuid = yaml_profile["uuid"]
28
+ metadata = yaml_profile["metadata"]
29
+ imports = yaml_profile["imports"]
30
+ merge = yaml_profile["merge"]
31
+ modify = yaml_profile["modify"]
32
+ back_matter = yaml_profile["back-matter"]
4
33
 
5
- def initialize(metadata, imports, controls)
6
- @metadata = metadata
7
- @imports = imports
8
- @controls = controls
34
+ Profile.new(uuid, metadata, imports, merge, modify, back_matter)
9
35
  end
10
36
  end
11
- end
37
+ end
@@ -1,32 +1,11 @@
1
- require_relative "serializer"
1
+ require_relative "base_class"
2
2
 
3
3
  module Oscal
4
- class Property
5
- include Serializer
4
+ class Property < Oscal::BaseClass
5
+ KEY = %i(name uuid ns value klass remarks)
6
6
 
7
- KEY = %i(name value)
8
7
  attr_accessor *KEY
9
- attr_serializable *KEY
10
-
11
- def self.wrap(obj)
12
- return obj if obj.is_a? Property
13
- return Property.new(obj) unless obj.is_a? Array
14
-
15
- obj.map do |x|
16
- Property.wrap(x)
17
- end
18
- end
19
8
 
20
- def initialize(options={})
21
- options.each_pair.each do |key,val|
22
- key_name = key.gsub('-','_')
23
-
24
- unless KEY.include?(key_name.to_sym)
25
- raise UnknownAttributeError.new("Unknown key `#{key}` in Property")
26
- end
27
-
28
- self.send("#{key_name}=", val)
29
- end
30
- end
9
+ attr_serializable *KEY
31
10
  end
32
11
  end
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Remove < Oscal::BaseClass
5
+ KEY = %i(by_name by_class by_id by_item_name by_ns)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Resource < Oscal::BaseClass
5
+ KEY = %i(uuid title description props docuement_ids citation rlinks
6
+ base64 remarks)
7
+
8
+ attr_accessor *KEY
9
+
10
+ attr_serializable *KEY
11
+
12
+ def set_value(key_name, val)
13
+ case key_name
14
+ when "props"
15
+ Property.wrap(val)
16
+ when "document_ids"
17
+ DocumentId.wrap(val)
18
+ when "citation"
19
+ Citation.wrap(val)
20
+ when "rlinks"
21
+ Rlink.wrap(val)
22
+ when "base64"
23
+ Base64Object.wrap(val)
24
+ else
25
+ val
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class ResponsibleParty < Oscal::BaseClass
5
+ KEY = %i(role_id party_uuids props links remakrs)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "party_uuids"
14
+ PartyUuid.wrap(val)
15
+ when "props"
16
+ Property.wrap(val)
17
+ when "links"
18
+ Link.wrap(val)
19
+ else
20
+ val
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Revision < Oscal::BaseClass
5
+ KEY = %i(title published last_modified version oscal_version
6
+ props links remarks)
7
+
8
+ attr_accessor *KEY
9
+
10
+ attr_serializable *KEY
11
+
12
+ def set_value(key_name, val)
13
+ case key_name
14
+ when "props"
15
+ Property.wrap(val)
16
+ when "links"
17
+ Link.wrap(val)
18
+ else
19
+ val
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Rlink < Oscal::BaseClass
5
+ KEY = %i(href media_type hashes)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "hashes"
14
+ HashObject.wrap(val)
15
+ else
16
+ val
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/oscal/role.rb ADDED
@@ -0,0 +1,22 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Role < Oscal::BaseClass
5
+ KEY = %i(id title short_name description props links remakrs)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "props"
14
+ Property.wrap(val)
15
+ when "links"
16
+ Link.wrap(val)
17
+ else
18
+ val
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Select < Oscal::BaseClass
5
+ KEY = %i(how_many choice)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "choice"
14
+ Choice.wrap(val)
15
+ else
16
+ val
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,10 +1,10 @@
1
- require 'yaml'
1
+ require "yaml"
2
2
 
3
3
  module Oscal
4
4
  module Serializer
5
5
  def to_h
6
6
  instance_variables.each_with_object({}) do |var, hash|
7
- var_name = var.to_s.delete('@')
7
+ var_name = var.to_s.delete("@")
8
8
  hash[var_name] = instance_variable_get(var)
9
9
  end
10
10
  end
@@ -36,7 +36,7 @@ module Oscal
36
36
  end
37
37
 
38
38
  def from_yaml(yaml_string)
39
- data = YAML.load(yaml_string)
39
+ data = YAML.safe_load(yaml_string)
40
40
  from_h(data)
41
41
  end
42
42
 
@@ -44,6 +44,17 @@ module Oscal
44
44
  raise NotImplementedError, "#{self}#from_xml not implemented!"
45
45
  end
46
46
 
47
+ # Psych >= 4 requires permitted_classes to load such classes
48
+ # https://github.com/ruby/psych/issues/533
49
+ def safe_load_yaml(path)
50
+ YAML.load_file(
51
+ path,
52
+ permitted_classes: [::Time, ::Date, ::DateTime],
53
+ )
54
+ rescue ArgumentError
55
+ YAML.load_file(path)
56
+ end
57
+
47
58
  private
48
59
 
49
60
  def attribute_names
@@ -54,7 +65,9 @@ module Oscal
54
65
  attrs.each do |attr|
55
66
  attribute_names << attr.to_sym
56
67
  define_method(attr) { instance_variable_get("@#{attr}") }
57
- define_method("#{attr}=") { |value| instance_variable_set("@#{attr}", value) }
68
+ define_method("#{attr}=") do |value|
69
+ instance_variable_set("@#{attr}", value)
70
+ end
58
71
  end
59
72
  end
60
73
  end
@@ -0,0 +1,31 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class SetParameter < Oscal::BaseClass
5
+ KEY = %i(param_id klass depneds_on props links label usage
6
+ constraints guidelines values select)
7
+
8
+ attr_accessor *KEY
9
+
10
+ attr_serializable *KEY
11
+
12
+ def set_value(key_name, val)
13
+ case key_name
14
+ when "props"
15
+ Property.wrap(val)
16
+ when "links"
17
+ Link.wrap(val)
18
+ when "constraints"
19
+ Constraint.wrap(val)
20
+ when "guidelines"
21
+ Guideline.wrap(val)
22
+ when "values"
23
+ Value.wrap(val)
24
+ when "select"
25
+ Select.wrap(val)
26
+ else
27
+ val
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class TelephoneNumber < Oscal::BaseClass
5
+ KEY = %i(type number)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
data/lib/oscal/test.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Test < Oscal::BaseClass
5
+ KEY = %i(expression remarks)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
data/lib/oscal/url.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Url < Oscal::BaseClass
5
+ KEY = %i(val)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Value < Oscal::BaseClass
5
+ include Serializer
6
+
7
+ KEY = %i(val)
8
+ attr_accessor *KEY
9
+
10
+ attr_serializable *KEY
11
+
12
+ def self.wrap(obj)
13
+ return obj if obj.is_a? Value
14
+ return Value.new(obj) unless obj.is_a? Array
15
+
16
+ obj.map do |x|
17
+ Value.wrap(x)
18
+ end
19
+ end
20
+
21
+ def initialize(options = {})
22
+ unless options.is_a? Hash
23
+ options = { "val" => options }
24
+ end
25
+
26
+ options.each_pair.each do |key, val|
27
+ key_name = key.gsub("-", "_")
28
+
29
+ unless KEY.include?(key_name.to_sym)
30
+ raise UnknownAttributeError.new("Unknown key `#{key}` in Value")
31
+ end
32
+
33
+ self.send("#{key_name}=", val)
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/oscal/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oscal
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,40 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class WithId < Oscal::BaseClass
5
+ include Serializer
6
+
7
+ KEY = %i(val)
8
+
9
+ attr_accessor *KEY
10
+
11
+ attr_serializable *KEY
12
+
13
+ def self.wrap(obj)
14
+ return obj if obj.is_a? WithId
15
+ return WithId.new(obj) unless obj.is_a? Array
16
+
17
+ obj.map do |x|
18
+ WithId.wrap(x)
19
+ end
20
+ end
21
+
22
+ def initialize(options = {})
23
+ klass = self.class
24
+
25
+ unless options.is_a? Hash
26
+ options = { KEY.first.to_s => options }
27
+ end
28
+
29
+ options.each_pair.each do |key, val|
30
+ key_name = key.gsub("-", "_")
31
+
32
+ unless KEY.include?(key_name.to_sym)
33
+ raise UnknownAttributeError.new("Unknown key `#{key}` in #{klass.name}")
34
+ end
35
+
36
+ send("#{key_name}=", val)
37
+ end
38
+ end
39
+ end
40
+ end