Module: ASModel::CRUD
- Defined in:
- backend/app/model/ASModel_crud.rb
Overview
Code for converting JSONModels into DB records and back again.
Defined Under Namespace
Modules: ClassMethods
Class Method Summary collapse
Instance Method Summary collapse
-
#apply_nested_records(json, new_record = false) ⇒ Object
Several JSONModels consist of logical subrecords that are stored as separate models in the database (in separate tables).
-
#delete ⇒ Object
Delete the current record using Sequel’s delete method, but clean up dependencies first.
-
#eagerly_load! ⇒ Object
Do whatever is necessary to eaglerly load this object from the database.
-
#map_validation_to_json_property(columns, property) ⇒ Object
When reporting a Sequel validation error against the set of ‘columns’, report it against the JSONModel ‘property’ instead.
-
#mark_as_system_modified ⇒ Object
-
#remove_nested_records ⇒ Object
-
#system_modified? ⇒ Boolean
that their local copy of the record includes the system-generated data too.
-
#update_from_json(json, extra_values = {}, apply_nested_records = true) ⇒ Object
-
#validate ⇒ Object
Class Method Details
.included(base) ⇒ Object
8 9 10 11 12 |
# File 'backend/app/model/ASModel_crud.rb', line 8 def self.included(base) base.extend(ClassMethods) base.include(JSONModel) base.extend(JSONModel) end |
.set_audit_fields(json, obj) ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'backend/app/model/ASModel_crud.rb', line 17 def self.set_audit_fields(json, obj) ['created_by', 'last_modified_by'].each do |field| json[field] = obj[field.intern] if obj[field.intern] end ['system_mtime', 'user_mtime', 'create_time'].each do |field| val = obj[field.intern] next if !val json[field] = val.getutc.iso8601 end end |
Instance Method Details
#apply_nested_records(json, new_record = false) ⇒ Object
Several JSONModels consist of logical subrecords that are stored as separate models in the database (in separate tables).
When we get a JSON blob for a record with subrecords, we want to create a database record for each subrecords (or, if a URI referencing an existing subrecord was given, use the existing object), then associate those subrecords with the main record.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'backend/app/model/ASModel_crud.rb', line 60 def apply_nested_records(json, new_record = false) self.remove_nested_records if !new_record self.class.nested_records.each do |nested_record| # Read the subrecords from our JSON blob and fetch or create # the corresponding subrecord from the database. model = Kernel.const_get(nested_record[:association][:class_name]) if nested_record[:association][:type] === :one_to_one add_record_method = nested_record[:association][:name].to_s elsif nested_record[:association][:type] === :many_to_one add_record_method = "#{nested_record[:association][:name].to_s.singularize}=" else add_record_method = "add_#{nested_record[:association][:name].to_s.singularize}" end records = json[nested_record[:json_property]] is_array = true if nested_record[:association][:type] === :one_to_one || nested_record[:is_array] === false is_array = false records = [records] end updated_records = [] (records or []).each_with_index do |json_or_uri, i| next if json_or_uri.nil? db_record = nil begin needs_linking = true if json_or_uri.is_a? String # A URI. Just grab its database ID and look it up. db_record = model[JSONModel(nested_record[:jsonmodel]).id_for(json_or_uri)] updated_records << json_or_uri else # Create a database record for the JSON blob and return its ID subrecord_json = JSONModel(nested_record[:jsonmodel]).from_hash(json_or_uri, true, true) # The value of subrecord_json can be mutated by the various # transformations performed by the model layer. Make sure we # keep the modified version of the JSON here. updated_records << subrecord_json if model.respond_to? :ensure_exists # Give our classes an opportunity to provide their own logic here db_record = model.ensure_exists(subrecord_json, self) else extra_opts = {} if nested_record[:association][:key] extra_opts[nested_record[:association][:key]] = self.id # We'll skip the call to the .add method because this step # will have already linked the nested record to this one. needs_linking = false end db_record = model.create_from_json(subrecord_json, extra_opts) end end if db_record.system_modified? # If the subrecord got changed by the system, mark ourselves as # modified too. self.mark_as_system_modified end self.send(add_record_method, db_record) if (db_record && needs_linking) rescue Sequel::ValidationFailed => e # Modify the exception keys by prefixing each with the path up until this point. e.instance_eval do if @errors prefix = nested_record[:json_property] prefix = "#{prefix}/#{i}" if is_array new_errors = {} @errors.each do |k, v| new_errors["#{prefix}/#{k}"] = v end @errors = new_errors end end raise e end end json[nested_record[:json_property]] = is_array ? updated_records : updated_records[0] end end |
#delete ⇒ Object
Delete the current record using Sequel’s delete method, but clean up dependencies first.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'backend/app/model/ASModel_crud.rb', line 224 def delete object_graph = self.object_graph deleted_uris = [] successfully_deleted_models = [] last_error = nil while true progressed = false object_graph.each do |model, ids_to_delete| next if successfully_deleted_models.include?(model) begin model.handle_delete(ids_to_delete) successfully_deleted_models << model progressed = true rescue Sequel::DatabaseError last_error = $! next end if model.my_jsonmodel(true) ids_to_delete.each do |id| deleted_model = model.my_jsonmodel(true) deleted_uri = deleted_model.uri_for(id, :repo_id => model.active_repository) if deleted_uri deleted_uris << deleted_uri end end end end break if object_graph.models.length == successfully_deleted_models.length unless progressed if last_error && DB.is_retriable_exception(last_error) # Give us a chance to retry after a deadlock raise last_error end raise ConflictException.new("Record deletion failed: #{last_error}") end end deleted_uris.each do |uri| Tombstone.create(:uri => uri) DB.after_commit do RealtimeIndexing.record_delete(uri) end end end |
#eagerly_load! ⇒ Object
Do whatever is necessary to eaglerly load this object from the database.
This is designed to give mixins the options of eagerly loading an entire record and its components.
48 49 50 |
# File 'backend/app/model/ASModel_crud.rb', line 48 def eagerly_load! # Do nothing by default end |
#map_validation_to_json_property(columns, property) ⇒ Object
When reporting a Sequel validation error against the set of ‘columns’, report it against the JSONModel ‘property’ instead.
For example, an identifier that must be unique to a repository might have a constraint against the columns [:repository, :identifier], but when we report this to the client we just want to tell them that the value for ‘identifier’ was incorrect.
287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'backend/app/model/ASModel_crud.rb', line 287 def map_validation_to_json_property(columns, property) errors = self.errors.clone self.errors.clear errors.each do |error, msg| if error == columns self.errors[property] = msg else self.errors[error] = msg end end end |
#mark_as_system_modified ⇒ Object
314 315 316 |
# File 'backend/app/model/ASModel_crud.rb', line 314 def mark_as_system_modified @system_modified = true end |
#remove_nested_records ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'backend/app/model/ASModel_crud.rb', line 156 def remove_nested_records self.class.nested_records.each do |nested_record_defn| if [:one_to_one, :one_to_many].include?(nested_record_defn[:association][:type]) # If the current record "owns" its nested record, delete the nested record. model = Kernel.const_get(nested_record_defn[:association][:class_name]) # Tell the nested record to clear its own nested records Array(self.send(nested_record_defn[:association][:name])).each do |nested_record| nested_record.delete end elsif nested_record_defn[:association][:type] === :many_to_many # Just remove the links self.send("remove_all_#{nested_record_defn[:association][:name]}".intern) elsif nested_record_defn[:association][:type] === :many_to_one # Just remove the link self.send("#{nested_record_defn[:association][:name].intern}=", nil) end end end |
#system_modified? ⇒ Boolean
that their local copy of the record includes the system-generated data too.
309 310 311 |
# File 'backend/app/model/ASModel_crud.rb', line 309 def system_modified? @system_modified end |
#update_from_json(json, extra_values = {}, apply_nested_records = true) ⇒ Object
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'backend/app/model/ASModel_crud.rb', line 177 def update_from_json(json, extra_values = {}, apply_nested_records = true) if self.values.has_key?(:suppressed) if self[:suppressed] == 1 raise ReadOnlyException.new("Can't update an object that has been suppressed") end # No funny business. If you want to set this you need to do it via the # dedicated controller. json["suppressed"] = false end schema_defined_properties = json.class.schema["properties"].map {|prop, defn| prop if !defn['readonly'] }.compact # Start by assuming all existing properties were nil, then overlay the # updates plus any extra attributes. # # This has the effect of unsetting (or setting to NULL) any properties that # were removed by this update. updated = Hash[schema_defined_properties.map {|property| [property, nil]}]. merge(json.to_hash). merge(ASUtils.keys_as_strings(extra_values)) if updated.has_key?('lock_version') && !updated['lock_version'] raise ConflictException.new("You must provide a lock_version in your request") end self.class.strict_param_setting = false self.update(self.class.prepare_for_db(json.class, updated). merge(:user_mtime => Time.now, :last_modified_by => RequestContext.get(:current_username))) if apply_nested_records self.apply_nested_records(json) end self.class.fire_update(json, self) self end |
#validate ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 |
# File 'backend/app/model/ASModel_crud.rb', line 31 def validate # Check uniqueness constraints self.class.repo_unique_constraints.each do |constraint| validates_unique([:repo_id, constraint[:property]], :message => constraint[:message]) map_validation_to_json_property([:repo_id, constraint[:property]], constraint[:json_property]) end super end |