cocina-models 0.29.0 → 0.30.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -3
- data/README.md +11 -0
- data/cocina-models.gemspec +5 -0
- data/exe/generator +9 -0
- data/lib/cocina/generator.rb +18 -0
- data/lib/cocina/generator/generator.rb +80 -0
- data/lib/cocina/generator/schema.rb +84 -0
- data/lib/cocina/generator/schema_array.rb +20 -0
- data/lib/cocina/generator/schema_base.rb +49 -0
- data/lib/cocina/generator/schema_ref.rb +16 -0
- data/lib/cocina/generator/schema_value.rb +60 -0
- data/lib/cocina/generator/vocab.rb +63 -0
- data/lib/cocina/models.rb +51 -25
- data/lib/cocina/models/access.rb +10 -0
- data/lib/cocina/models/admin_policy.rb +13 -56
- data/lib/cocina/models/admin_policy_administrative.rb +11 -0
- data/lib/cocina/models/administrative.rb +14 -0
- data/lib/cocina/models/catalog_link.rb +4 -1
- data/lib/cocina/models/collection.rb +21 -31
- data/lib/cocina/models/collection_identification.rb +9 -0
- data/lib/cocina/models/description.rb +1 -8
- data/lib/cocina/models/dro.rb +34 -70
- data/lib/cocina/models/dro_access.rb +16 -0
- data/lib/cocina/models/dro_structural.rb +14 -0
- data/lib/cocina/models/embargo.rb +16 -0
- data/lib/cocina/models/file.rb +20 -36
- data/lib/cocina/models/file_administrative.rb +10 -0
- data/lib/cocina/models/file_set.rb +8 -15
- data/lib/cocina/models/file_set_structural.rb +9 -0
- data/lib/cocina/models/geographic.rb +10 -0
- data/lib/cocina/models/identification.rb +11 -0
- data/lib/cocina/models/message_digest.rb +17 -0
- data/lib/cocina/models/presentation.rb +12 -0
- data/lib/cocina/models/release_tag.rb +12 -7
- data/lib/cocina/models/request_admin_policy.rb +15 -3
- data/lib/cocina/models/request_collection.rb +21 -4
- data/lib/cocina/models/request_dro.rb +32 -11
- data/lib/cocina/models/request_dro_structural.rb +13 -0
- data/lib/cocina/models/request_file.rb +15 -6
- data/lib/cocina/models/request_file_set.rb +7 -9
- data/lib/cocina/models/request_file_set_structural.rb +9 -0
- data/lib/cocina/models/sequence.rb +2 -5
- data/lib/cocina/models/title.rb +12 -0
- data/lib/cocina/models/validator.rb +22 -0
- data/lib/cocina/models/version.rb +1 -1
- data/lib/cocina/models/vocab.rb +45 -60
- data/openapi.yml +695 -0
- metadata +101 -9
- data/lib/cocina/models/admin_policy_attributes.rb +0 -21
- data/lib/cocina/models/collection_attributes.rb +0 -22
- data/lib/cocina/models/dro_attributes.rb +0 -22
- data/lib/cocina/models/file_attributes.rb +0 -25
- data/lib/cocina/models/file_set_attributes.rb +0 -16
- data/lib/cocina/models/types.rb +0 -10
data/lib/cocina/models.rb
CHANGED
@@ -5,23 +5,34 @@ require 'zeitwerk'
|
|
5
5
|
require 'dry-struct'
|
6
6
|
require 'dry-types'
|
7
7
|
require 'json'
|
8
|
+
require 'openapi_parser'
|
9
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
10
|
+
require 'yaml'
|
8
11
|
|
9
12
|
# Help Zeitwerk find some of our classes
|
10
13
|
class CocinaModelsInflector < Zeitwerk::Inflector
|
14
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
15
|
+
# rubocop:disable Metrics/MethodLength
|
11
16
|
def camelize(basename, _abspath)
|
12
17
|
case basename
|
13
18
|
when 'dro'
|
14
19
|
'DRO'
|
15
|
-
when 'dro_builder'
|
16
|
-
'DROBuilder'
|
17
20
|
when 'request_dro'
|
18
21
|
'RequestDRO'
|
22
|
+
when 'dro_access'
|
23
|
+
'DROAccess'
|
24
|
+
when 'dro_structural'
|
25
|
+
'DROStructural'
|
26
|
+
when 'request_dro_structural'
|
27
|
+
'RequestDROStructural'
|
19
28
|
when 'version'
|
20
29
|
'VERSION'
|
21
30
|
else
|
22
31
|
super
|
23
32
|
end
|
24
33
|
end
|
34
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
35
|
+
# rubocop:enable Metrics/MethodLength
|
25
36
|
end
|
26
37
|
|
27
38
|
loader = Zeitwerk::Loader.new
|
@@ -36,43 +47,58 @@ module Cocina
|
|
36
47
|
# Raised when the type attribute is not valid.
|
37
48
|
class UnknownTypeError < Error; end
|
38
49
|
|
50
|
+
# Raised when an error occurs validating against openapi.
|
51
|
+
class ValidationError < Error; end
|
52
|
+
|
39
53
|
# Base class for Cocina Structs
|
40
54
|
class Struct < Dry::Struct
|
41
55
|
transform_keys(&:to_sym)
|
56
|
+
schema schema.strict
|
57
|
+
end
|
58
|
+
|
59
|
+
# DRY Types
|
60
|
+
module Types
|
61
|
+
include Dry.Types()
|
42
62
|
end
|
43
63
|
|
44
64
|
# @param [Hash] dyn a ruby hash representation of the JSON serialization of a collection or DRO
|
45
|
-
# @
|
65
|
+
# @param [boolean] validate
|
66
|
+
# @return [DRO,Collection,AdminPolicy]
|
46
67
|
# @raises [UnknownTypeError] if a valid type is not found in the data
|
47
68
|
# @raises [KeyError] if a type field cannot be found in the data
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
69
|
+
# @raises [ValidationError] if hash representation fails openapi validation
|
70
|
+
def self.build(dyn, validate: true)
|
71
|
+
clazz = case dyn.fetch('type')
|
72
|
+
when *DRO::TYPES
|
73
|
+
DRO
|
74
|
+
when *Collection::TYPES
|
75
|
+
Collection
|
76
|
+
when *AdminPolicy::TYPES
|
77
|
+
AdminPolicy
|
78
|
+
else
|
79
|
+
raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
|
80
|
+
end
|
81
|
+
clazz.new(dyn, validate: validate)
|
59
82
|
end
|
60
83
|
|
61
84
|
# @param [Hash] dyn a ruby hash representation of the JSON serialization of a request for a Collection or DRO
|
85
|
+
# @param [boolean] validate
|
62
86
|
# @return [RequestDRO,RequestCollection,RequestAdminPolicy]
|
63
87
|
# @raises [UnknownTypeError] if a valid type is not found in the data
|
64
88
|
# @raises [KeyError] if a type field cannot be found in the data
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
89
|
+
# @raises [ValidationError] if hash representation fails openapi validation
|
90
|
+
def self.build_request(dyn, validate: true)
|
91
|
+
clazz = case dyn.fetch('type')
|
92
|
+
when *DRO::TYPES
|
93
|
+
RequestDRO
|
94
|
+
when *Collection::TYPES
|
95
|
+
RequestCollection
|
96
|
+
when *AdminPolicy::TYPES
|
97
|
+
RequestAdminPolicy
|
98
|
+
else
|
99
|
+
raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
|
100
|
+
end
|
101
|
+
clazz.new(dyn, validate: validate)
|
76
102
|
end
|
77
103
|
end
|
78
104
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
class Access < Struct
|
6
|
+
# Access level
|
7
|
+
attribute :access, Types::Strict::String.default('dark').enum('world', 'stanford', 'location-based', 'citation-only', 'dark').meta(omittable: true)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -2,67 +2,24 @@
|
|
2
2
|
|
3
3
|
module Cocina
|
4
4
|
module Models
|
5
|
-
# An admin policy object.
|
6
5
|
class AdminPolicy < Struct
|
7
6
|
include Checkable
|
8
7
|
|
9
|
-
TYPES = [
|
10
|
-
Vocab.admin_policy
|
11
|
-
].freeze
|
8
|
+
TYPES = ['http://cocina.sul.stanford.edu/models/admin_policy.jsonld'].freeze
|
12
9
|
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# Subschema for administrative concerns
|
18
|
-
class Administrative < Struct
|
19
|
-
# This was copied from the ActiveFedora defaults: Dor::AdminPolicyObject.new.defaultObjectRights.content
|
20
|
-
DEFAULT_OBJECT_RIGHTS = <<~XML
|
21
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
22
|
-
|
23
|
-
<rightsMetadata>
|
24
|
-
<access type="discover">
|
25
|
-
<machine>
|
26
|
-
<world/>
|
27
|
-
</machine>
|
28
|
-
</access>
|
29
|
-
<access type="read">
|
30
|
-
<machine>
|
31
|
-
<world/>
|
32
|
-
</machine>
|
33
|
-
</access>
|
34
|
-
<use>
|
35
|
-
<human type="useAndReproduction"/>
|
36
|
-
<human type="creativeCommons"/>
|
37
|
-
<machine type="creativeCommons" uri=""/>
|
38
|
-
<human type="openDataCommons"/>
|
39
|
-
<machine type="openDataCommons" uri=""/>
|
40
|
-
</use>
|
41
|
-
<copyright>
|
42
|
-
<human/>
|
43
|
-
</copyright>
|
44
|
-
</rightsMetadata>
|
45
|
-
XML
|
46
|
-
|
47
|
-
# An XML blob that is to be used temporarily until we model rights
|
48
|
-
attribute :default_object_rights, Types::Strict::String.optional.default(DEFAULT_OBJECT_RIGHTS)
|
49
|
-
|
50
|
-
# which workflow to start when registering (used by Web Archive apos to start wasCrawlPreassemblyWF)
|
51
|
-
attribute :registration_workflow, Types::String.optional.default(nil)
|
52
|
-
|
53
|
-
# TODO: Allowing hasAdminPolicy to be omittable for now (until rolled out to consumers),
|
54
|
-
# but I think it's actually required for every Admin Policy
|
55
|
-
attribute :hasAdminPolicy, Types::Strict::String.optional.default(nil)
|
56
|
-
end
|
57
|
-
|
58
|
-
class Identification < Struct
|
59
|
-
end
|
60
|
-
|
61
|
-
class Structural < Struct
|
62
|
-
end
|
63
|
-
|
64
|
-
include AdminPolicyAttributes
|
10
|
+
# example: item
|
11
|
+
attribute :type, Types::Strict::String.enum(*AdminPolicy::TYPES)
|
12
|
+
# example: druid:bc123df4567
|
65
13
|
attribute :externalIdentifier, Types::Strict::String
|
14
|
+
attribute :label, Types::Strict::String
|
15
|
+
attribute :version, Types::Strict::Integer
|
16
|
+
attribute(:administrative, AdminPolicyAdministrative.default { AdminPolicyAdministrative.new })
|
17
|
+
attribute :description, Description.optional.meta(omittable: true)
|
18
|
+
|
19
|
+
def self.new(attributes = default_attributes, safe = false, validate = true, &block)
|
20
|
+
Validator.validate(self, attributes.with_indifferent_access) if validate
|
21
|
+
super(attributes, safe, &block)
|
22
|
+
end
|
66
23
|
end
|
67
24
|
end
|
68
25
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
class AdminPolicyAdministrative < Struct
|
6
|
+
attribute :defaultObjectRights, Types::Strict::String.default('<?xml version="1.0" encoding="UTF-8"?><rightsMetadata><access type="discover"><machine><world/></machine></access><access type="read"><machine><world/></machine></access><use><human type="useAndReproduction"/><human type="creativeCommons"/><machine type="creativeCommons" uri=""/><human type="openDataCommons"/><machine type="openDataCommons" uri=""/></use><copyright><human/></copyright></rightsMetadata>').meta(omittable: true)
|
7
|
+
attribute :registrationWorkflow, Types::Strict::String.meta(omittable: true)
|
8
|
+
attribute :hasAdminPolicy, Types::Strict::String.meta(omittable: true)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
class Administrative < Struct
|
6
|
+
# example: druid:bc123df4567
|
7
|
+
attribute :hasAdminPolicy, Types::Strict::String.meta(omittable: true)
|
8
|
+
attribute :releaseTags, Types::Strict::Array.of(ReleaseTag).meta(omittable: true)
|
9
|
+
# Administrative or Internal project this resource is a part of
|
10
|
+
# example: Google Books
|
11
|
+
attribute :partOfProject, Types::Strict::String.meta(omittable: true)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -2,9 +2,12 @@
|
|
2
2
|
|
3
3
|
module Cocina
|
4
4
|
module Models
|
5
|
-
# Metadata for a catalog link
|
6
5
|
class CatalogLink < Struct
|
6
|
+
# Catalog that is the source of the linked record.
|
7
|
+
# example: symphony
|
7
8
|
attribute :catalog, Types::Strict::String
|
9
|
+
# Record identifier that is unique within the context of the linked record's catalog.
|
10
|
+
# example: 11403803
|
8
11
|
attribute :catalogRecordId, Types::Strict::String
|
9
12
|
end
|
10
13
|
end
|
@@ -2,43 +2,33 @@
|
|
2
2
|
|
3
3
|
module Cocina
|
4
4
|
module Models
|
5
|
-
# A digital repository collection.
|
6
|
-
# See http://sul-dlss.github.io/cocina-models/maps/Collection.json
|
7
5
|
class Collection < Struct
|
8
6
|
include Checkable
|
9
7
|
|
10
|
-
TYPES = [
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
Vocab.user_collection
|
16
|
-
].freeze
|
8
|
+
TYPES = ['http://cocina.sul.stanford.edu/models/collection.jsonld',
|
9
|
+
'http://cocina.sul.stanford.edu/models/curated-collection.jsonld',
|
10
|
+
'http://cocina.sul.stanford.edu/models/user-collection.jsonld',
|
11
|
+
'http://cocina.sul.stanford.edu/models/exhibit.jsonld',
|
12
|
+
'http://cocina.sul.stanford.edu/models/series.jsonld'].freeze
|
17
13
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# Identification sub-schema for the Collection
|
33
|
-
class Identification < Struct
|
34
|
-
attribute :catalogLinks, Types::Strict::Array.of(CatalogLink).meta(omittable: true)
|
35
|
-
end
|
14
|
+
# The content type of the Collection. Selected from an established set of values.
|
15
|
+
# example: item
|
16
|
+
attribute :type, Types::Strict::String.enum(*Collection::TYPES)
|
17
|
+
# example: druid:bc123df4567
|
18
|
+
attribute :externalIdentifier, Types::Strict::String
|
19
|
+
# Primary processing label (can be same as title) for a Collection.
|
20
|
+
attribute :label, Types::Strict::String
|
21
|
+
# Version for the Collection within SDR.
|
22
|
+
attribute :version, Types::Strict::Integer
|
23
|
+
attribute(:access, Access.default { Access.new })
|
24
|
+
attribute :administrative, Administrative.optional.meta(omittable: true)
|
25
|
+
attribute :description, Description.optional.meta(omittable: true)
|
26
|
+
attribute :identification, CollectionIdentification.optional.meta(omittable: true)
|
36
27
|
|
37
|
-
|
28
|
+
def self.new(attributes = default_attributes, safe = false, validate = true, &block)
|
29
|
+
Validator.validate(self, attributes.with_indifferent_access) if validate
|
30
|
+
super(attributes, safe, &block)
|
38
31
|
end
|
39
|
-
|
40
|
-
include CollectionAttributes
|
41
|
-
attribute :externalIdentifier, Types::Strict::String
|
42
32
|
end
|
43
33
|
end
|
44
34
|
end
|
@@ -2,15 +2,8 @@
|
|
2
2
|
|
3
3
|
module Cocina
|
4
4
|
module Models
|
5
|
-
# Descriptive metadata. See http://sul-dlss.github.io/cocina-models/maps/Description.json
|
6
5
|
class Description < Struct
|
7
|
-
|
8
|
-
class Title < Struct
|
9
|
-
attribute :primary, Types::Strict::Bool
|
10
|
-
attribute :titleFull, Types::Strict::String
|
11
|
-
end
|
12
|
-
|
13
|
-
attribute :title, Types::Strict::Array.of(Title)
|
6
|
+
attribute :title, Types::Strict::Array.of(Title).default([].freeze)
|
14
7
|
end
|
15
8
|
end
|
16
9
|
end
|
data/lib/cocina/models/dro.rb
CHANGED
@@ -2,80 +2,44 @@
|
|
2
2
|
|
3
3
|
module Cocina
|
4
4
|
module Models
|
5
|
-
# A digital repository object.
|
6
|
-
# See http://sul-dlss.github.io/cocina-models/maps/DRO.json
|
7
5
|
class DRO < Struct
|
8
6
|
include Checkable
|
9
7
|
|
10
|
-
TYPES = [
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# Subschema for embargo concerns
|
31
|
-
class Embargo < Struct
|
32
|
-
attribute :releaseDate, Types::Params::DateTime
|
33
|
-
attribute :access, Types::String.default('dark')
|
34
|
-
.enum('world', 'stanford', 'location-based', 'citation-only', 'dark')
|
35
|
-
attribute :useAndReproductionStatement, Types::Strict::String.meta(omittable: true)
|
36
|
-
end
|
37
|
-
|
38
|
-
attribute :access, Types::String.default('dark')
|
39
|
-
.enum('world', 'stanford', 'location-based', 'citation-only', 'dark')
|
40
|
-
attribute :copyright, Types::Strict::String.meta(omittable: true)
|
41
|
-
attribute :embargo, Embargo.optional.meta(omittable: true)
|
42
|
-
attribute :useAndReproductionStatement, Types::Strict::String.meta(omittable: true)
|
43
|
-
end
|
44
|
-
|
45
|
-
# Subschema for administrative concerns
|
46
|
-
class Administrative < Struct
|
47
|
-
# TODO: Allowing hasAdminPolicy to be omittable for now (until rolled out to consumers),
|
48
|
-
# but I think it's actually required for every DRO
|
49
|
-
attribute :hasAdminPolicy, Types::Strict::String.optional.default(nil)
|
50
|
-
attribute :releaseTags, Types::Strict::Array.of(ReleaseTag).meta(omittable: true).default([].freeze)
|
51
|
-
attribute :partOfProject, Types::Strict::String.meta(omittable: true)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Identification sub-schema for the DRO
|
55
|
-
class Identification < Struct
|
56
|
-
attribute :sourceId, Types::Strict::String.meta(omittable: true)
|
57
|
-
attribute :catalogLinks, Types::Strict::Array.of(CatalogLink).meta(omittable: true)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Geographic sub-schema for the DRO
|
61
|
-
class Geographic < Struct
|
62
|
-
attribute :iso19139, Types::Strict::String
|
63
|
-
end
|
64
|
-
|
65
|
-
# Structural sub-schema for the DRO (uses FileSet, unlike RequestDRO which uses RequestFileSet)
|
66
|
-
class Structural < Struct
|
67
|
-
attribute :contains, Types::Strict::Array.of(FileSet).meta(omittable: true)
|
68
|
-
attribute :hasAgreement, Types::Strict::String.meta(omittable: true)
|
69
|
-
attribute :isMemberOf, Types::Strict::String.meta(omittable: true)
|
70
|
-
attribute :hasMemberOrders, Types::Strict::Array.of(Sequence).meta(omittable: true)
|
71
|
-
end
|
72
|
-
|
73
|
-
include DroAttributes
|
8
|
+
TYPES = ['http://cocina.sul.stanford.edu/models/object.jsonld',
|
9
|
+
'http://cocina.sul.stanford.edu/models/3d.jsonld',
|
10
|
+
'http://cocina.sul.stanford.edu/models/agreement.jsonld',
|
11
|
+
'http://cocina.sul.stanford.edu/models/book.jsonld',
|
12
|
+
'http://cocina.sul.stanford.edu/models/document.jsonld',
|
13
|
+
'http://cocina.sul.stanford.edu/models/geo.jsonld',
|
14
|
+
'http://cocina.sul.stanford.edu/models/image.jsonld',
|
15
|
+
'http://cocina.sul.stanford.edu/models/page.jsonld',
|
16
|
+
'http://cocina.sul.stanford.edu/models/photograph.jsonld',
|
17
|
+
'http://cocina.sul.stanford.edu/models/manuscript.jsonld',
|
18
|
+
'http://cocina.sul.stanford.edu/models/map.jsonld',
|
19
|
+
'http://cocina.sul.stanford.edu/models/media.jsonld',
|
20
|
+
'http://cocina.sul.stanford.edu/models/track.jsonld',
|
21
|
+
'http://cocina.sul.stanford.edu/models/webarchive-binary.jsonld',
|
22
|
+
'http://cocina.sul.stanford.edu/models/webarchive-seed.jsonld'].freeze
|
23
|
+
|
24
|
+
# The content type of the DRO. Selected from an established set of values.
|
25
|
+
# example: item
|
26
|
+
attribute :type, Types::Strict::String.enum(*DRO::TYPES)
|
27
|
+
# example: druid:bc123df4567
|
74
28
|
attribute :externalIdentifier, Types::Strict::String
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
29
|
+
# Primary processing label (can be same as title) for a DRO.
|
30
|
+
attribute :label, Types::Strict::String
|
31
|
+
# Version for the DRO within SDR.
|
32
|
+
attribute :version, Types::Strict::Integer
|
33
|
+
attribute(:access, DROAccess.default { DROAccess.new })
|
34
|
+
attribute :administrative, Administrative.optional.meta(omittable: true)
|
35
|
+
attribute :description, Description.optional.meta(omittable: true)
|
36
|
+
attribute :identification, Identification.optional.meta(omittable: true)
|
37
|
+
attribute :structural, DROStructural.optional.meta(omittable: true)
|
38
|
+
attribute :geographic, Geographic.optional.meta(omittable: true)
|
39
|
+
|
40
|
+
def self.new(attributes = default_attributes, safe = false, validate = true, &block)
|
41
|
+
Validator.validate(self, attributes.with_indifferent_access) if validate
|
42
|
+
super(attributes, safe, &block)
|
79
43
|
end
|
80
44
|
end
|
81
45
|
end
|