Module: Relationships::ClassMethods
- Defined in:
- backend/app/model/mixins/relationships.rb
Instance Method Summary (collapse)
-
- (Object) add_relationship_dependency(relationship_name, clz)
-
- (Object) apply_relationships(obj, json, opts, new_record = false)
Create set of relationships for a given update.
-
- (Object) calculate_object_graph(object_graph, opts = {})
-
- (Object) clear_relationships
Reset relationship definitions for the current class.
-
- (Object) create_from_json(json, opts = {})
-
- (Object) define_relationship(opts)
Define a new relationship.
-
- (Object) delete_existing_relationships(obj, bump_lock_version_on_referent = false, force = false, predicate = nil)
Delete all existing relationships for ‘obj’.
-
- (Object) dependent_models
-
- (Object) eager_load_relationships(objects)
Find all of the relationships involving ‘objects’ and tell each object to cache its relationships.
-
- (Object) find_relationship(name, noerror = false)
-
- (Object) instances_relating_to(obj)
Find all instances of the referring class that have a relationship with ‘obj’ Spans all defined relationships.
-
- (Object) relationship_dependencies
-
- (Object) relationships
-
- (Object) sequel_to_jsonmodel(objs, opts = {})
-
- (Object) touch_mtime_of_anyone_related_to(obj)
This notifies the current model that an instance of a related model has been changed.
-
- (Object) transfer(relationship_name, target, victims)
Instance Method Details
- (Object) add_relationship_dependency(relationship_name, clz)
693 694 695 696 |
# File 'backend/app/model/mixins/relationships.rb', line 693 def add_relationship_dependency(relationship_name, clz) @relationship_dependencies[relationship_name] ||= [] @relationship_dependencies[relationship_name] << clz end |
- (Object) apply_relationships(obj, json, opts, new_record = false)
Create set of relationships for a given update
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 |
# File 'backend/app/model/mixins/relationships.rb', line 571 def apply_relationships(obj, json, opts, new_record = false) delete_existing_relationships(obj) if !new_record @relationships.each do |relationship_name, relationship_defn| property_name = relationship_defn.json_property # If there's no property name, the relationship is just read-only next if !property_name # For each record reference in our JSON data ASUtils.as_array(json[property_name]).each_with_index do |reference, idx| record_type = parse_reference(reference['ref'], opts) referent_model = relationship_defn.participating_models.find {|model| model.my_jsonmodel.record_type == record_type[:type] } or raise "Couldn't find model for #{record_type[:type]}" referent = referent_model[record_type[:id]] if !referent raise ReferenceError.new("Can't relate to non-existent record: #{reference['ref']}") end # Create a new relationship instance linking us and them together, and # add the properties from the JSON request to the relationship properties = reference.clone.tap do |properties| properties.delete('ref') end properties[:aspace_relationship_position] = idx properties[:system_mtime] = Time.now properties[:user_mtime] = Time.now relationship_defn.relate(obj, referent, properties) # If this is a reciprocal relationship (defined on both participating # models), update the referent's lock version to ensure that a # concurrent update to that object won't clobber our changes. if referent_model.find_relationship(relationship_name, true) && !opts[:system_generated] DB.increase_lock_version_or_fail(referent) end end end end |
- (Object) calculate_object_graph(object_graph, opts = {})
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 |
# File 'backend/app/model/mixins/relationships.rb', line 441 def calculate_object_graph(object_graph, opts = {}) # For each relationship involving a resource self.relationships.each do |relationship_defn| # Find any relationship of this type involving any record mentioned in # object graph object_graph.each do |model, id_list| next unless relationship_defn.participating_models.include?(model) linked_relationships = relationship_defn.find_by_participant_ids(model, id_list).map {|row| row[:id] } object_graph.add_objects(relationship_defn, linked_relationships) end end super end |
- (Object) clear_relationships
Reset relationship definitions for the current class
463 464 465 |
# File 'backend/app/model/mixins/relationships.rb', line 463 def clear_relationships @relationships = {} end |
- (Object) create_from_json(json, opts = {})
634 635 636 637 638 |
# File 'backend/app/model/mixins/relationships.rb', line 634 def create_from_json(json, opts = {}) obj = super apply_relationships(obj, json, opts, true) obj end |
- (Object) define_relationship(opts)
Define a new relationship.
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
# File 'backend/app/model/mixins/relationships.rb', line 488 def define_relationship(opts) [:name, :contains_references_to_types].each do |p| opts[p] or raise "No #{p} given" end base = self ArchivesSpaceService.loaded_hook do # We hold off actually setting anything up until all models have been # loaded, since our relationships may need to reference a model that # hasn't been loaded yet. # # This is also why the :contains_references_to_types property is a proc # instead of a regular array--we don't want to blow up with a NameError # if the model hasn't been loaded yet. = opts[:contains_references_to_types].call clz = Class.new(AbstractRelationship) do table = "#{opts[:name]}_rlshp".intern set_dataset(table) set_primary_key(:id) if !self.db.table_exists?(self.table_name) Log.warn("Table doesn't exist: #{self.table_name}") end set_participating_models([base, *].uniq) set_json_property(opts[:json_property]) set_wants_array(opts[:is_array].nil? || opts[:is_array]) end opts[:class_callback].call(clz) if opts[:class_callback] @relationships[opts[:name]] = clz .each do |model| model.include(Relationships) model.add_relationship_dependency(opts[:name], base) end end end |
- (Object) delete_existing_relationships(obj, bump_lock_version_on_referent = false, force = false, predicate = nil)
Delete all existing relationships for ‘obj’.
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'backend/app/model/mixins/relationships.rb', line 534 def delete_existing_relationships(obj, bump_lock_version_on_referent = false, force = false, predicate = nil) relationships.each do |relationship_defn| next if (!relationship_defn.json_property && !force) if (relationship_defn.json_property && (!self.my_jsonmodel.schema['properties'][relationship_defn.json_property] || self.my_jsonmodel.schema['properties'][relationship_defn.json_property]['readonly'] === 'true')) # Don't delete instances of relationships that are read-only in this direction. next end relationship_defn.find_by_participant(obj).each do |relationship| # If our predicate says to spare this relationship, leave it alone next if predicate && !predicate.call(relationship) # If we're deleting a relationship without replacing it, bump the lock # version on the referent object so it doesn't accidentally get # re-added. # # This will also encourage the indexer to pick up changes on deletion # (e.g. a subject gets deleted and we want to reindex the records that # reference it) if bump_lock_version_on_referent referent = relationship.other_referent_than(obj) DB.increase_lock_version_or_fail(referent) if referent end relationship.delete end end end |
- (Object) dependent_models
478 479 480 |
# File 'backend/app/model/mixins/relationships.rb', line 478 def dependent_models @relationship_dependencies.values.flatten.uniq end |
- (Object) eager_load_relationships(objects)
Find all of the relationships involving ‘objects’ and tell each object to cache its relationships. This is an optimisation: avoids the need for one SELECT for every relationship lookup by pulling back all relationships at once.
622 623 624 625 626 627 628 629 630 631 |
# File 'backend/app/model/mixins/relationships.rb', line 622 def eager_load_relationships(objects) relationships.each do |relationship_defn| # For each defined relationship relationships_map = relationship_defn.find_by_participants(objects) objects.each do |obj| obj.cache_relationships(relationship_defn, relationships_map[obj]) end end end |
- (Object) find_relationship(name, noerror = false)
483 484 485 |
# File 'backend/app/model/mixins/relationships.rb', line 483 def find_relationship(name, noerror = false) @relationships[name] or (noerror ? nil : raise("Couldn't find #{name} in #{@relationships.inspect}")) end |
- (Object) instances_relating_to(obj)
Find all instances of the referring class that have a relationship with ‘obj’ Spans all defined relationships.
686 687 688 689 690 |
# File 'backend/app/model/mixins/relationships.rb', line 686 def instances_relating_to(obj) relationships.map {|relationship_defn| relationship_defn.who_participates_with(obj) }.flatten end |
- (Object) relationship_dependencies
473 474 475 |
# File 'backend/app/model/mixins/relationships.rb', line 473 def relationship_dependencies @relationship_dependencies end |
- (Object) relationships
468 469 470 |
# File 'backend/app/model/mixins/relationships.rb', line 468 def relationships @relationships.values end |
- (Object) sequel_to_jsonmodel(objs, opts = {})
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 |
# File 'backend/app/model/mixins/relationships.rb', line 641 def sequel_to_jsonmodel(objs, opts = {}) jsons = super return jsons if opts[:skip_relationships] eager_load_relationships(objs) jsons.zip(objs).each do |json, obj| relationships.each do |relationship_defn| property_name = relationship_defn.json_property # If we don't need this property in our return JSON, skip it. next unless property_name # For each defined relationship relationships = if obj.cached_relationships # Use the eagerly fetched relationships if we have them Array(obj.cached_relationships[relationship_defn]) else relationship_defn.find_by_participant(obj) end json[property_name] = relationships.map {|relationship| next if RequestContext.get(:enforce_suppression) && relationship.suppressed == 1 # Return the relationship properties, plus the URI reference of the # related object values = ASUtils.keys_as_strings(relationship.properties) values['ref'] = relationship.uri_for_other_referent_than(obj) values } if !relationship_defn.wants_array? json[property_name] = json[property_name].first end end end jsons end |
- (Object) touch_mtime_of_anyone_related_to(obj)
This notifies the current model that an instance of a related model has been changed. We respond by finding any of our own instances that refer to the updated instance and update their mtime.
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 |
# File 'backend/app/model/mixins/relationships.rb', line 707 def (obj) now = Time.now relationships.map do |relationship_defn| models = relationship_defn.participating_models if models.include?(obj.class) their_ref_columns = relationship_defn.reference_columns_for(obj.class) my_ref_columns = relationship_defn.reference_columns_for(self) their_ref_columns.each do |their_col| my_ref_columns.each do |my_col| # Example: if we're updating a subject record and want to update # the timestamps of any linked archival object records: # # * self = ArchivalObject # * relationship_defn is subject_rlshp # * obj = #<Subject instance that was updated> # * their_col = subject_rlshp.subject_id # * my_col = subject_rlshp.archival_object_id if DB.supports_join_updates? if self.table_name == :agent_software && relationship_defn.table_name == :linked_agents_rlshp # Terrible to have to do this, but the MySQL optimizer refuses # to use the primary key on agent_software because it (often) # only has one row. DB.open do |db| id_str = Integer(obj.id).to_s db.run("UPDATE `agent_software` FORCE INDEX (PRIMARY) " + " INNER JOIN `linked_agents_rlshp` " + "ON (`linked_agents_rlshp`.`agent_software_id` = `agent_software`.`id`) " + "SET `agent_software`.`system_mtime` = NOW() " + "WHERE (`linked_agents_rlshp`.`archival_object_id` = #{id_str})") end else # MySQL will optimize this much more aggressively self.join(relationship_defn, Sequel.qualify(relationship_defn.table_name, my_col) => Sequel.qualify(self.table_name, :id)). filter(Sequel.qualify(relationship_defn.table_name, their_col) => obj.id). update(Sequel.qualify(self.table_name, :system_mtime) => now) end else ids_to_touch = relationship_defn.filter(their_col => obj.id). select(my_col) self.filter(:id => ids_to_touch). update(:system_mtime => now) end end end end end end |
- (Object) transfer(relationship_name, target, victims)
699 700 701 702 |
# File 'backend/app/model/mixins/relationships.rb', line 699 def transfer(relationship_name, target, victims) relationship = find_relationship(relationship_name) relationship.transfer(target, victims) end |