aspace_client 0.0.1
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 +7 -0
- data/lib/aspace_client.rb +15 -0
- data/lib/aspace_client/archivesspace_json_schema.rb +210 -0
- data/lib/aspace_client/asutils.rb +142 -0
- data/lib/aspace_client/client_enum_source.rb +30 -0
- data/lib/aspace_client/exceptions.rb +15 -0
- data/lib/aspace_client/helpers.rb +0 -0
- data/lib/aspace_client/json_schema_concurrency_fix.rb +52 -0
- data/lib/aspace_client/json_schema_utils.rb +414 -0
- data/lib/aspace_client/jsonmodel.rb +342 -0
- data/lib/aspace_client/jsonmodel_client.rb +528 -0
- data/lib/aspace_client/jsonmodel_i18n_mixin.rb +77 -0
- data/lib/aspace_client/jsonmodel_type.rb +478 -0
- data/lib/aspace_client/memoryleak.rb +59 -0
- data/lib/aspace_client/schemas/abstract_agent.rb +51 -0
- data/lib/aspace_client/schemas/abstract_agent_relationship.rb +12 -0
- data/lib/aspace_client/schemas/abstract_archival_object.rb +96 -0
- data/lib/aspace_client/schemas/abstract_classification.rb +44 -0
- data/lib/aspace_client/schemas/abstract_name.rb +23 -0
- data/lib/aspace_client/schemas/abstract_note.rb +13 -0
- data/lib/aspace_client/schemas/accession.rb +174 -0
- data/lib/aspace_client/schemas/accession_parts_relationship.rb +31 -0
- data/lib/aspace_client/schemas/accession_sibling_relationship.rb +31 -0
- data/lib/aspace_client/schemas/active_edits.rb +23 -0
- data/lib/aspace_client/schemas/advanced_query.rb +12 -0
- data/lib/aspace_client/schemas/agent_contact.rb +25 -0
- data/lib/aspace_client/schemas/agent_corporate_entity.rb +32 -0
- data/lib/aspace_client/schemas/agent_family.rb +29 -0
- data/lib/aspace_client/schemas/agent_person.rb +31 -0
- data/lib/aspace_client/schemas/agent_relationship_associative.rb +28 -0
- data/lib/aspace_client/schemas/agent_relationship_earlierlater.rb +28 -0
- data/lib/aspace_client/schemas/agent_relationship_parentchild.rb +26 -0
- data/lib/aspace_client/schemas/agent_relationship_subordinatesuperior.rb +26 -0
- data/lib/aspace_client/schemas/agent_software.rb +22 -0
- data/lib/aspace_client/schemas/archival_object.rb +60 -0
- data/lib/aspace_client/schemas/archival_record_children.rb +15 -0
- data/lib/aspace_client/schemas/boolean_field_query.rb +13 -0
- data/lib/aspace_client/schemas/boolean_query.rb +13 -0
- data/lib/aspace_client/schemas/classification.rb +10 -0
- data/lib/aspace_client/schemas/classification_term.rb +38 -0
- data/lib/aspace_client/schemas/classification_tree.rb +17 -0
- data/lib/aspace_client/schemas/collection_management.rb +27 -0
- data/lib/aspace_client/schemas/container.rb +29 -0
- data/lib/aspace_client/schemas/container_location.rb +19 -0
- data/lib/aspace_client/schemas/date.rb +19 -0
- data/lib/aspace_client/schemas/date_field_query.rb +14 -0
- data/lib/aspace_client/schemas/deaccession.rb +20 -0
- data/lib/aspace_client/schemas/defaults.rb +104 -0
- data/lib/aspace_client/schemas/digital_object.rb +64 -0
- data/lib/aspace_client/schemas/digital_object_component.rb +53 -0
- data/lib/aspace_client/schemas/digital_object_tree.rb +19 -0
- data/lib/aspace_client/schemas/digital_record_children.rb +15 -0
- data/lib/aspace_client/schemas/enumeration.rb +29 -0
- data/lib/aspace_client/schemas/enumeration_migration.rb +14 -0
- data/lib/aspace_client/schemas/event.rb +88 -0
- data/lib/aspace_client/schemas/extent.rb +17 -0
- data/lib/aspace_client/schemas/external_document.rb +12 -0
- data/lib/aspace_client/schemas/external_id.rb +11 -0
- data/lib/aspace_client/schemas/field_query.rb +15 -0
- data/lib/aspace_client/schemas/file_version.rb +26 -0
- data/lib/aspace_client/schemas/group.rb +17 -0
- data/lib/aspace_client/schemas/instance.rb +27 -0
- data/lib/aspace_client/schemas/job.rb +57 -0
- data/lib/aspace_client/schemas/location.rb +36 -0
- data/lib/aspace_client/schemas/location_batch.rb +45 -0
- data/lib/aspace_client/schemas/merge_request.rb +48 -0
- data/lib/aspace_client/schemas/name_corporate_entity.rb +15 -0
- data/lib/aspace_client/schemas/name_family.rb +13 -0
- data/lib/aspace_client/schemas/name_form.rb +15 -0
- data/lib/aspace_client/schemas/name_person.rb +19 -0
- data/lib/aspace_client/schemas/name_software.rb +14 -0
- data/lib/aspace_client/schemas/note_abstract.rb +17 -0
- data/lib/aspace_client/schemas/note_bibliography.rb +29 -0
- data/lib/aspace_client/schemas/note_bioghist.rb +22 -0
- data/lib/aspace_client/schemas/note_chronology.rb +28 -0
- data/lib/aspace_client/schemas/note_citation.rb +32 -0
- data/lib/aspace_client/schemas/note_definedlist.rb +25 -0
- data/lib/aspace_client/schemas/note_digital_object.rb +23 -0
- data/lib/aspace_client/schemas/note_index.rb +29 -0
- data/lib/aspace_client/schemas/note_index_item.rb +25 -0
- data/lib/aspace_client/schemas/note_multipart.rb +25 -0
- data/lib/aspace_client/schemas/note_orderedlist.rb +27 -0
- data/lib/aspace_client/schemas/note_outline.rb +20 -0
- data/lib/aspace_client/schemas/note_outline_level.rb +21 -0
- data/lib/aspace_client/schemas/note_singlepart.rb +24 -0
- data/lib/aspace_client/schemas/note_text.rb +17 -0
- data/lib/aspace_client/schemas/permission.rb +15 -0
- data/lib/aspace_client/schemas/preference.rb +16 -0
- data/lib/aspace_client/schemas/record_tree.rb +17 -0
- data/lib/aspace_client/schemas/repository.rb +32 -0
- data/lib/aspace_client/schemas/repository_with_agent.rb +14 -0
- data/lib/aspace_client/schemas/resource.rb +112 -0
- data/lib/aspace_client/schemas/resource_tree.rb +20 -0
- data/lib/aspace_client/schemas/rights_statement.rb +35 -0
- data/lib/aspace_client/schemas/subject.rb +30 -0
- data/lib/aspace_client/schemas/term.rb +16 -0
- data/lib/aspace_client/schemas/user.rb +56 -0
- data/lib/aspace_client/schemas/user_defined.rb +42 -0
- data/lib/aspace_client/schemas/vocabulary.rb +15 -0
- data/lib/aspace_client/validations.rb +434 -0
- data/lib/aspace_client/validator_cache.rb +47 -0
- data/lib/aspace_client/version.rb +3 -0
- metadata +244 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
|
|
2
|
+
module JSONModelI18nMixin
|
|
3
|
+
|
|
4
|
+
def t(*args)
|
|
5
|
+
JSONModel::init_args[:i18n_source].t(*args)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def _exceptions
|
|
9
|
+
exceptions = super
|
|
10
|
+
|
|
11
|
+
return exceptions unless JSONModel::init_args[:i18n_source]
|
|
12
|
+
|
|
13
|
+
already_translated = exceptions.instance_variable_get(:@translated) || false
|
|
14
|
+
|
|
15
|
+
unless already_translated
|
|
16
|
+
[:errors, :warnings].each do |level|
|
|
17
|
+
next unless exceptions[level]
|
|
18
|
+
exceptions[level].clone.each do |path, msgs|
|
|
19
|
+
exceptions[level][path] = msgs.map{|m| translate_exception_message(m)}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
exceptions.instance_variable_set(:@translated, true)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
exceptions
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def translate_exception_message(msg)
|
|
31
|
+
msg_data = case msg
|
|
32
|
+
when "Can't be empty"
|
|
33
|
+
[:cant_be_empty]
|
|
34
|
+
when "entered value didn't match password"
|
|
35
|
+
[:password_did_not_match]
|
|
36
|
+
when "Group code must be unique within a repository"
|
|
37
|
+
[:group_code_already_in_use]
|
|
38
|
+
when "Property is required but was missing"
|
|
39
|
+
[:missing_required_property]
|
|
40
|
+
when "Property was missing"
|
|
41
|
+
[:missing_property]
|
|
42
|
+
when "Not a valid date", "not a valid date"
|
|
43
|
+
[:not_a_valid_date]
|
|
44
|
+
when "is required unless a begin or end date is given"
|
|
45
|
+
[:is_required_unless_a_begin_or_end_date_is_given]
|
|
46
|
+
when "is required unless an expression or an end date is given"
|
|
47
|
+
[:is_required_unless_an_expression_or_an_end_date_is_given]
|
|
48
|
+
when "That ID is already in use"
|
|
49
|
+
[:id_already_in_use]
|
|
50
|
+
when /^Username '(.+)' is already in use/
|
|
51
|
+
[:username_already_in_use, {:username => $1}]
|
|
52
|
+
when /^Did not match regular expression: (.+)/
|
|
53
|
+
[:did_not_match_regular_expression, {:regexp => $1}]
|
|
54
|
+
when /^Must be at least ([0-9]+) characters/
|
|
55
|
+
[:too_few_characters, {:min_length => $1}]
|
|
56
|
+
when /^Must be ([0-9]+) characters or fewer/
|
|
57
|
+
[:too_many_characters, {:max_length => $1}]
|
|
58
|
+
when /^At least ([0-9]+) item\(s\) is required/
|
|
59
|
+
[:too_few_items, {:min_items => $1}]
|
|
60
|
+
when /^Invalid value '(.*?)'. Must be one of: (.*)/
|
|
61
|
+
[:invalid_value, {:value => $1, :valid_set => $2}]
|
|
62
|
+
when /^Must be a (.*?) \(you provided a (.*)\)/
|
|
63
|
+
[:wrong_type, {:desired_type => $1, :actual_type => $2}]
|
|
64
|
+
when /^Must be one of: (.*?) \(you provided a (.*)\)/
|
|
65
|
+
[:must_be_one_of, {:allowed_types => $1, :actual_type => $2}]
|
|
66
|
+
when /Username '(.*)' is already in use/
|
|
67
|
+
[:username_already_in_use, {:username => $1}]
|
|
68
|
+
else
|
|
69
|
+
[msg.downcase.gsub(/[\s,':]/, '_')]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
key, vars = *msg_data
|
|
73
|
+
t("validation_errors.#{key.to_s}", vars)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# A common base class for all JSONModel classes
|
|
2
|
+
class JSONModelType
|
|
3
|
+
|
|
4
|
+
# Class instance variables store the bits specific to this model
|
|
5
|
+
def self.init(type, schema, mixins = [])
|
|
6
|
+
@record_type = type
|
|
7
|
+
@schema = schema
|
|
8
|
+
|
|
9
|
+
# In client mode, mix in some extra convenience methods for querying the
|
|
10
|
+
# ArchivesSpace backend service via HTTP.
|
|
11
|
+
if JSONModel.client_mode?
|
|
12
|
+
require_relative 'jsonmodel_client'
|
|
13
|
+
include JSONModel::Client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
define_accessors(schema['properties'].keys)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
mixins.each do |mixin|
|
|
21
|
+
include(mixin)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Return the JSON schema that defines this JSONModel class
|
|
27
|
+
def self.schema
|
|
28
|
+
find_ancestor_class_instance(:@schema)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Return the version number of this JSONModel's schema
|
|
33
|
+
def self.schema_version
|
|
34
|
+
self.schema['version']
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Return the type of this JSONModel class (a keyword like
|
|
39
|
+
# :archival_object)
|
|
40
|
+
def self.record_type
|
|
41
|
+
find_ancestor_class_instance(:@record_type)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def self.to_s
|
|
46
|
+
"JSONModel(:#{self.record_type})"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Add a custom validation to this model type.
|
|
51
|
+
#
|
|
52
|
+
# The validation is a block that takes a hash of properties and returns an array of pairs like:
|
|
53
|
+
# [["propertyname", "the problem with it"], ...]
|
|
54
|
+
def self.add_validation(name, level = :error, &block)
|
|
55
|
+
raise "Validation name already taken: #{name}" if JSONModel.custom_validations[name]
|
|
56
|
+
|
|
57
|
+
JSONModel.custom_validations[name] = block
|
|
58
|
+
|
|
59
|
+
self.schema["validations"] ||= []
|
|
60
|
+
self.schema["validations"] << [level, name]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Create an instance of this JSONModel from the data contained in 'hash'.
|
|
65
|
+
def self.from_hash(hash, raise_errors = true, trusted = false)
|
|
66
|
+
hash["jsonmodel_type"] = self.record_type.to_s
|
|
67
|
+
|
|
68
|
+
# If we're running in client mode, leave 'readonly' properties in place,
|
|
69
|
+
# since they're intended for use by clients. Otherwise, we drop them.
|
|
70
|
+
drop_system_properties = !JSONModel.client_mode?
|
|
71
|
+
|
|
72
|
+
if trusted
|
|
73
|
+
# We got this data from a trusted source (such as another JSONModel
|
|
74
|
+
# that had already been validated itself). No need to double up
|
|
75
|
+
self.new(hash, true)
|
|
76
|
+
else
|
|
77
|
+
cleaned = JSONSchemaUtils.drop_unknown_properties(hash, self.schema, drop_system_properties)
|
|
78
|
+
cleaned = ASUtils.jsonmodels_to_hashes(cleaned)
|
|
79
|
+
|
|
80
|
+
validate(cleaned, raise_errors)
|
|
81
|
+
|
|
82
|
+
self.new(cleaned)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Create an instance of this JSONModel from a JSON string.
|
|
88
|
+
def self.from_json(s, raise_errors = true)
|
|
89
|
+
self.from_hash(ASUtils.json_parse(s), raise_errors)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def self.uri_and_remaining_options_for(id = nil, opts = {})
|
|
94
|
+
# Some schemas (like name schemas) don't have a URI because they don't
|
|
95
|
+
# need endpoints. That's fine.
|
|
96
|
+
if not self.schema['uri']
|
|
97
|
+
return nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
uri = self.schema['uri']
|
|
101
|
+
|
|
102
|
+
if not id.nil?
|
|
103
|
+
uri += "/#{URI.escape(id.to_s)}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
self.substitute_parameters(uri, opts)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Given a numeric internal ID and additional options produce a pair containing a URI reference.
|
|
111
|
+
# For example:
|
|
112
|
+
#
|
|
113
|
+
# JSONModel(:archival_object).uri_for(500, :repo_id => 123)
|
|
114
|
+
#
|
|
115
|
+
# might yield "/repositories/123/archival_objects/500"
|
|
116
|
+
#
|
|
117
|
+
def self.uri_for(id = nil, opts = {})
|
|
118
|
+
result = self.uri_and_remaining_options_for(id, opts)
|
|
119
|
+
|
|
120
|
+
result ? result[0] : nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# The inverse of uri_for:
|
|
125
|
+
#
|
|
126
|
+
# JSONModel(:archival_object).id_for("/repositories/123/archival_objects/500", :repo_id => 123)
|
|
127
|
+
#
|
|
128
|
+
# might yield 500
|
|
129
|
+
#
|
|
130
|
+
def self.id_for(uri, opts = {}, noerror = false)
|
|
131
|
+
if not self.schema['uri']
|
|
132
|
+
if noerror
|
|
133
|
+
return nil
|
|
134
|
+
else
|
|
135
|
+
raise "Missing a URI definition for class #{self.class}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
pattern = self.schema['uri']
|
|
140
|
+
pattern = pattern.gsub(/\/:[a-zA-Z_]+\//, '/[^/ ]+/')
|
|
141
|
+
|
|
142
|
+
# IDs are either positive integers, or importer-provided logical IDs
|
|
143
|
+
id_regexp = /([0-9]+|import_[a-f0-9-]+)/
|
|
144
|
+
|
|
145
|
+
if uri =~ /#{pattern}\/#{id_regexp}(\#.*)?$/
|
|
146
|
+
return id_to_int($1)
|
|
147
|
+
elsif uri =~ /#{pattern.gsub(/\[\^\/ \]\+\/tree/, '')}#{id_regexp}\/tree$/
|
|
148
|
+
return id_to_int($1)
|
|
149
|
+
else
|
|
150
|
+
if noerror
|
|
151
|
+
nil
|
|
152
|
+
else
|
|
153
|
+
raise "Couldn't make an ID out of URI: #{uri}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Return the type of the schema property defined by 'path'
|
|
160
|
+
#
|
|
161
|
+
# For example, type_of("names/items/type") might return a JSONModel class
|
|
162
|
+
def self.type_of(path)
|
|
163
|
+
type = JSONSchemaUtils.schema_path_lookup(self.schema, path)["type"]
|
|
164
|
+
|
|
165
|
+
ref = JSONModel.parse_jsonmodel_ref(type)
|
|
166
|
+
|
|
167
|
+
if ref
|
|
168
|
+
JSONModel.JSONModel(ref.first)
|
|
169
|
+
else
|
|
170
|
+
Kernel.const_get(type.capitalize)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def initialize(params = {}, trusted = false)
|
|
177
|
+
set_data(params)
|
|
178
|
+
|
|
179
|
+
@uri ||= params['uri']
|
|
180
|
+
|
|
181
|
+
# a hash to store transient instance data
|
|
182
|
+
@instance_data = {}
|
|
183
|
+
|
|
184
|
+
self.class.define_accessors(@data.keys)
|
|
185
|
+
|
|
186
|
+
if trusted
|
|
187
|
+
@validated = {}
|
|
188
|
+
@cleaned_data = @data
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
attr_reader :uri
|
|
194
|
+
|
|
195
|
+
def uri=(val)
|
|
196
|
+
@uri = val
|
|
197
|
+
self['uri'] = val
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def instance_data
|
|
201
|
+
@instance_data
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def [](key)
|
|
206
|
+
@data[key.to_s]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def []=(key, val)
|
|
211
|
+
@validated = false
|
|
212
|
+
@data[key.to_s] = val
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def has_key?(key)
|
|
217
|
+
@data.has_key?(key)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# Validate the current JSONModel instance and return a list of exceptions
|
|
222
|
+
# produced.
|
|
223
|
+
def _exceptions
|
|
224
|
+
return @validated if @validated && @errors.nil?
|
|
225
|
+
|
|
226
|
+
exceptions = {}
|
|
227
|
+
if not @always_valid
|
|
228
|
+
exceptions = self.validate(@data, false)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
if @errors
|
|
232
|
+
exceptions[:errors] = (exceptions[:errors] or {}).merge(@errors)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
exceptions
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def clear_errors
|
|
240
|
+
# reset validation
|
|
241
|
+
@validated = false
|
|
242
|
+
@errors = nil
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def add_error(attribute, message)
|
|
247
|
+
# reset validation
|
|
248
|
+
@validated = false
|
|
249
|
+
|
|
250
|
+
# call JSONModel::Client's version
|
|
251
|
+
super
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _warnings
|
|
256
|
+
exceptions = self._exceptions
|
|
257
|
+
|
|
258
|
+
if exceptions.has_key?(:warnings)
|
|
259
|
+
exceptions[:warnings]
|
|
260
|
+
else
|
|
261
|
+
[]
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# Set this object instance to always pass validation. Used so the
|
|
267
|
+
# frontend can create intentionally incomplete objects that will be filled
|
|
268
|
+
# out by the user.
|
|
269
|
+
def _always_valid!
|
|
270
|
+
@always_valid = true
|
|
271
|
+
self
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Update the values of the current JSONModel instance with the contents of
|
|
276
|
+
# 'params', validating before accepting the update.
|
|
277
|
+
def update(params)
|
|
278
|
+
@validated = false
|
|
279
|
+
replace(ASUtils.deep_merge(@data, params))
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# Replace the values of the current JSONModel instance with the contents
|
|
284
|
+
# of 'params', validating before accepting the replacement.
|
|
285
|
+
def replace(params)
|
|
286
|
+
@validated = false
|
|
287
|
+
set_data(params)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def reset_from(another_jsonmodel)
|
|
292
|
+
@data = another_jsonmodel.instance_eval { @data }
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def to_s
|
|
297
|
+
"#<JSONModel(:#{self.class.record_type}) #{@data.inspect}>"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def inspect
|
|
302
|
+
self.to_s
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# Produce a (possibly nested) hash from the values of this JSONModel. Any
|
|
307
|
+
# values that don't appear in the JSON schema will not appear in the
|
|
308
|
+
# result.
|
|
309
|
+
def to_hash(mode = nil)
|
|
310
|
+
mode = (mode || :validated)
|
|
311
|
+
|
|
312
|
+
raise "Invalid .to_hash mode: #{mode}" unless [:trusted, :validated, :raw].include?(mode)
|
|
313
|
+
|
|
314
|
+
return @data if mode == :raw
|
|
315
|
+
|
|
316
|
+
if @validated and @cleaned_data
|
|
317
|
+
return @cleaned_data
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
cleaned = JSONSchemaUtils.drop_unknown_properties(@data, self.class.schema)
|
|
321
|
+
cleaned = ASUtils.jsonmodels_to_hashes(cleaned)
|
|
322
|
+
|
|
323
|
+
if mode == :validated
|
|
324
|
+
@validated = false
|
|
325
|
+
self.validate(cleaned)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
@cleaned_data = cleaned
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# Produce a JSON string from the values of this JSONModel. Any values
|
|
333
|
+
# that don't appear in the JSON schema will not appear in the result.
|
|
334
|
+
def to_json(opts = {})
|
|
335
|
+
ASUtils.to_json(self.to_hash(opts[:mode]), opts.is_a?(Hash) ? opts.merge(:max_nesting => false) : {})
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# Return the internal ID of this JSONModel.
|
|
340
|
+
def id
|
|
341
|
+
ref = JSONModel::parse_reference(self.uri)
|
|
342
|
+
|
|
343
|
+
if ref
|
|
344
|
+
ref[:id]
|
|
345
|
+
else
|
|
346
|
+
nil
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
protected
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def validate(data, raise_errors = true)
|
|
355
|
+
@validated = self.class.validate(data, raise_errors)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# Validate the supplied hash using the JSON schema for this model. Raise
|
|
360
|
+
# a ValidationException if there are any fatal validation problems, or if
|
|
361
|
+
# strict mode is enabled and warnings were produced.
|
|
362
|
+
def self.validate(hash, raise_errors = true)
|
|
363
|
+
|
|
364
|
+
properties = JSONSchemaUtils.drop_unknown_properties(hash, self.schema)
|
|
365
|
+
ValidatorCache.with_validator_for(self, properties) do |validator|
|
|
366
|
+
|
|
367
|
+
messages = validator.validate
|
|
368
|
+
exceptions = JSONSchemaUtils.parse_schema_messages(messages, validator)
|
|
369
|
+
|
|
370
|
+
if raise_errors && (!exceptions[:errors].empty? || (JSONModel.strict_mode? && !exceptions[:warnings].empty?))
|
|
371
|
+
raise JSONModel::ValidationException.new(:invalid_object => self.new(hash),
|
|
372
|
+
:warnings => exceptions[:warnings],
|
|
373
|
+
:errors => exceptions[:errors],
|
|
374
|
+
:attribute_types => exceptions[:attribute_types])
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
exceptions.reject{|k, v| v.empty?}
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# Given a URI template like /repositories/:repo_id/something/:somevar, and
|
|
383
|
+
# a hash containing keys and replacement strings, return [uri, opts],
|
|
384
|
+
# where 'uri' is the template with values substituted for their
|
|
385
|
+
# placeholders, and 'opts' is any parameters that weren't consumed.
|
|
386
|
+
#
|
|
387
|
+
def self.substitute_parameters(uri, opts = {})
|
|
388
|
+
matched = []
|
|
389
|
+
opts.each do |k, v|
|
|
390
|
+
old = uri
|
|
391
|
+
uri = uri.gsub(":#{k}", URI.escape(v.to_s))
|
|
392
|
+
|
|
393
|
+
if old != uri
|
|
394
|
+
|
|
395
|
+
if v.is_a? Symbol
|
|
396
|
+
raise ("Tried to substitute the value '#{v.inspect}' for ':#{k}'." +
|
|
397
|
+
" This is usually a sign that something has gone wrong" +
|
|
398
|
+
" further up the stack. (URI was: '#{uri}')")
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Matched on this parameter. Remove it from the passed in hash
|
|
402
|
+
matched << k
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
if uri.include?(":")
|
|
407
|
+
raise "Template substitution was incomplete: '#{uri}'"
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
remaining_opts = opts.clone
|
|
411
|
+
matched.each do |k|
|
|
412
|
+
remaining_opts.delete(k)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
[uri, remaining_opts]
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
private
|
|
420
|
+
|
|
421
|
+
# If this class is subclassed, we won't be able to see our class instance
|
|
422
|
+
# variables unless we explicitly look up the inheritance chain.
|
|
423
|
+
def self.find_ancestor_class_instance(variable)
|
|
424
|
+
@ancestor_instance_variables ||= Atomic.new({})
|
|
425
|
+
|
|
426
|
+
if !@ancestor_instance_variables.value[variable]
|
|
427
|
+
self.ancestors.each do |clz|
|
|
428
|
+
val = clz.instance_variable_get(variable)
|
|
429
|
+
if val
|
|
430
|
+
@ancestor_instance_variables.update {|vs| vs.merge({variable => val})}
|
|
431
|
+
break
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
@ancestor_instance_variables.value[variable]
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
# Define accessors for all variable names listed in 'attributes'
|
|
441
|
+
def self.define_accessors(attributes)
|
|
442
|
+
attributes.each do |attribute|
|
|
443
|
+
|
|
444
|
+
if not method_defined? "#{attribute}"
|
|
445
|
+
define_method "#{attribute}" do
|
|
446
|
+
@data[attribute]
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
if not method_defined? "#{attribute}="
|
|
452
|
+
define_method "#{attribute}=" do |value|
|
|
453
|
+
@validated = false
|
|
454
|
+
@data[attribute] = JSONModel.clean_data(value)
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def self.id_to_int(id)
|
|
462
|
+
if id =~ /^import_/
|
|
463
|
+
id
|
|
464
|
+
else
|
|
465
|
+
id.to_i
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def set_data(data)
|
|
471
|
+
hash = JSONModel.clean_data(data)
|
|
472
|
+
hash["jsonmodel_type"] = self.class.record_type.to_s
|
|
473
|
+
hash = JSONSchemaUtils.apply_schema_defaults(hash, self.class.schema)
|
|
474
|
+
|
|
475
|
+
@data = hash
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
end
|