Module: Jinx::Resource
- Includes:
- Inversible, Mergeable
- Defined in:
- lib/jinx/resource.rb,
lib/jinx/resource/matcher.rb
Overview
This Resource module enhances application domain classes with the following features:
-
meta-data introspection
-
dependency
-
inverse integrity
-
defaults
-
validation
-
copy/merge
A application domain module becomes jinxed by including Resource and specifying the Java package and optional JRuby class mix-in definitions.
Defined Under Namespace
Classes: DetailPrinter, Matcher, ReferencePrinter
Constant Summary collapse
- COPY_MERGE_OPTS =
The copy merge call options.
{:inverse => false}
- DEPENDENT_VISITOR =
The dependent attribute visitor.
Jinx::ReferenceVisitor.new { |obj| obj.class.dependent_attributes }
- DEF_MATCHER =
Matcher.new
Class Method Summary collapse
- .collection_value_equal?(value, other, matches = nil) ⇒ Boolean private
-
.match_all(sources, targets) ⇒ {Resouce => Resource}
A source => target hash of the given sources which match the targets using the #match_in method.
-
.value_equal?(value, other, matches = nil) ⇒ Boolean
Returns whether value equals other modulo the given matches according to the following tests: * value == other * value and other are Resource instances and value is a #match? with other.
Instance Method Summary collapse
-
#add_defaults ⇒ Resource
Sets the default attribute values for this domain object and its dependents.
-
#add_defaults_local ⇒ Object
private
Sets the default attribute values for this domain object.
-
#add_defaults_recursive ⇒ Object
protected
Adds the default values to this object, if necessary, and its dependents.
-
#alternate_key ⇒ Array, ...
The key value or values.
-
#clear_attribute(attribute) ⇒ Object
Clears the given attribute value.
-
#content_matches?(other) ⇒ Boolean
Matches this domain object with the other domain object.
-
#content_matches_recursive?(other, visited = Set.new) ⇒ Boolean
protected
Whether this object matches the other object on class and content.
-
#copy(*attributes) ⇒ Resource
Returns a new domain object with the given attributes copied from this domain object.
- #delegate_to_inverse_setter(prop, ref, writer) ⇒ Object private
-
#dependent? ⇒ Boolean
Whether this domain object is dependent on another entity.
-
#dependent_update_only?(other) ⇒ Boolean
Whether the other domain object is a dependent of this object and has an update-only non-domain attribute.
-
#dependents(properties = nil) ⇒ Enumerable
Returns this domain object’s dependents.
-
#difference(other, attributes = nil) ⇒ {Object => (Object,Object)}
(also: #diff)
Returns the difference between this Persistable and the other Persistable for the given attributes.
-
#direct_dependents(attribute) ⇒ <Resource>
Returns the attribute references which directly depend on this owner.
-
#dump {|owner| ... } ⇒ String
Prints this domain object’s content and recursively prints the referenced content.
-
#each_defaultable_reference {|dep| ... } ⇒ Object
private
Enumerates referenced domain objects for setting defaults.
-
#effective_owner_property_value ⇒ (Property, Resource)?
Returns the (property, value) pair for which there is an owner reference, or nil if this domain object does not reference an owner.
-
#empty_value(attribute) ⇒ Object
private
Returns 0 if attribute is a Java primitive number,
false
if attribute is a Java primitive boolean, an empty collectin if the Java attribute is a collection, nil otherwise. -
#independent? ⇒ Boolean
Whether this domain object is not dependent on another entity.
-
#java_type(attribute) ⇒ Object
private
Returns the Java type of the given attribute, or nil if attribute is not a Java property attribute.
-
#key(attributes = nil) ⇒ Array, ...
Returns the first non-nil #key_value for the primary, secondary and alternate key attributes.
-
#key_value(attributes) ⇒ Array, ...
Returns the key for the given key attributes as follows: * If there are no key attributes, then nil.
-
#mandatory_attributes ⇒ <Symbol>
Returns the attributes which are required for save.
-
#match_attribute_value(prop, newval, oldval) {|sources, targets| ... } ⇒ {Resource => Resource}
private
Returns the source => target hash of matches for the given prop newval sources and oldval targets.
-
#match_in(others) ⇒ Resource?
Matches this dependent domain object with the others on type and key attributes in the scope of a parent object.
-
#match_in_owner_scope(others) ⇒ Resource?
Returns the match of this domain object in the scope of a matching owner as follows: * If #match_in returns a match, then that match is the result is used.
-
#match_unique_object_with_attributes(others, attributes) ⇒ Object
private
Returns the object in others which uniquely matches this domain object on the given attributes, or nil if there is no unique match.
-
#matches?(other) ⇒ Boolean
Whether this object matches the fetched other object on class and a primary, secondary or alternate key.
-
#matches_attribute_value?(attribute, value) ⇒ Boolean
protected
Returns whether this Resource’s attribute value matches the given value.
-
#matches_key_attributes?(other, attributes) ⇒ Boolean
private
Whether there is a non-nil value for each attribute and the value matches the other attribute value.
-
#matches_without_owner_attribute?(other) ⇒ Boolean
private
Returns whether this domain object matches the other domain object as follows: * The classes are the same.
-
#minimal_match?(other) ⇒ Boolean
Returns the domain object in others which matches this dependent domain object within the scope of a parent on a minimally acceptable constraint.
-
#missing_mandatory_attributes ⇒ <Symbol>
protected
The required attributes for this domain object which are nil or empty.
-
#non_id_search_attribute_values ⇒ Object
private
Returns the attribute => value hash to use for matching this domain object.
-
#owner ⇒ Resource?
The domain object that owns this object, or nil if this object is not dependent on an owner.
-
#owner=(obj) ⇒ Object
Sets this dependent’s owner attribute to the given domain object.
-
#owner_ancestor?(other) ⇒ Boolean
Whether the other domain object is this object’s #owner or an #owner_ancestor? of this object’s #owner.
-
#path_value(path) ⇒ Object
Returns the value for the given attribute path Array or String expression, e.g.: study.path_value(“site.address.state”) follows the
study
->site
->address
->state
accessors and returns thestate
value, or nil if any intermediate reference is nil. -
#post_initialize ⇒ Object
Initialization hook.
-
#pretty_print(q) ⇒ String
The formatted content of this Resource.
-
#primary_key ⇒ Array, ...
The key value or values.
-
#print_class_and_id ⇒ Object
(also: #qp)
Prints this object’s class demodulized name and object id.
-
#printable_content(attributes = nil) {|ref| ... } ⇒ {Symbol => String}
Returns this domain object’s attributes content as an attribute => value hash suitable for printing.
-
#printable_value(value, &reference_printer) ⇒ Object
private
Returns a value suitable for printing.
-
#printworthy_attributes ⇒ <Symbol] the attributes to print
private
Returns an attribute => value hash which identifies the object.
-
#proxy_object_id ⇒ Integer
The object id.
-
#reachable?(other, path) ⇒ Boolean
Whether following there is a path from this object through the given properties to the other object.
-
#reference_hierarchy {|ref| ... } ⇒ Enumerable
Returns an enumerator on the transitive closure of the reference attributes.
-
#references(attributes = nil) ⇒ <Resource>
Returns the domain object references for the given attributes.
-
#search_attribute_values ⇒ Object
private
Returns the attribute => value hash to use for matching this domain object as follows: * If this domain object has a database identifier, then the identifier is the sole match criterion attribute.
-
#secondary_key ⇒ Array, ...
The key value or values.
-
#set_property_value(prop, value) ⇒ Object
Sets this domain object’s attribute to the value.
- #set_typed_property_value(property, value) ⇒ Object private
-
#to_s(attributes = nil) ⇒ String
(also: #inspect)
Prints this domain object in the format: class_name@object_id=> value … The default attributes include identifying attributes.
-
#validate ⇒ Resource
Validates this domain object and its ##dependents for consistency and completeness.
-
#validate_local ⇒ Object
private
Validates that this domain object is internally consistent.
-
#validate_mandatory_attributes ⇒ Object
private
Validates that this domain object contains a non-nil value for each mandatory attribute.
-
#validate_owner ⇒ Object
private
Validates that this domain object either doesn’t have an owner attribute or has a unique effective owner.
-
#visit_dependents {|dep| ... } ⇒ Object
Applies the operator block to the transitive closure of this domain object’s dependency relation.
-
#visit_owners {|dep| ... } ⇒ Object
Applies the operator block to the transitive closure of this domain object’s owner relation.
-
#visit_path(*path) {|attribute| ... } ⇒ Object
Applies the operator block to this object and each domain object in the reference path.
Methods included from Mergeable
#merge, #merge_attribute, #merge_attributes, #merge_domain_property_value, #merge_nondomain_property_value, #mergeable__equal?, #value_hash
Methods included from Inversible
#add_to_inverse_collection, #set_inverse, #set_inversible_noncollection_attribute
Class Method Details
.collection_value_equal?(value, other, matches = nil) ⇒ Boolean (private)
756 757 758 |
# File 'lib/jinx/resource.rb', line 756 def self.collection_value_equal?(value, other, matches=nil) value.size == other.size and value.all? { |v| other.include?(v) or (matches and other.include?(matches[v])) } end |
.match_all(sources, targets) ⇒ {Resouce => Resource}
Returns a source => target hash of the given sources which match the targets using the #match_in method.
420 421 422 |
# File 'lib/jinx/resource.rb', line 420 def self.match_all(sources, targets) DEF_MATCHER.match(sources, targets) end |
.value_equal?(value, other, matches = nil) ⇒ Boolean
Returns whether value equals other modulo the given matches according to the following tests:
-
value == other
-
value and other are Resource instances and value is a #match? with other.
-
value and other are Enumerable with members equal according to the above conditions.
-
value and other are DateTime instances and are equal to within one second.
The DateTime comparison accounts for differences in the Ruby -> Java -> Ruby roundtrip of a date attribute, which loses the seconds fraction.
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 |
# File 'lib/jinx/resource.rb', line 576 def self.value_equal?(value, other, matches=nil) value = value.to_ruby_date if Java::JavaUtil::Date === value other = other.to_ruby_date if Java::JavaUtil::Date === other if value == other then true elsif value.collection? and other.collection? then collection_value_equal?(value, other, matches) elsif Date === value and Date === other then (value - other).abs.floor.zero? elsif Resource === value and value.class === other then value.matches?(other) elsif matches then matches[value] == other else false end end |
Instance Method Details
#add_defaults ⇒ Resource
Sets the default attribute values for this domain object and its dependents. If this Resource does not have an identifier, then missing attributes are set to the values defined by Propertied#add_attribute_defaults.
Subclasses should override the private #add_defaults_local method rather than this method.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/jinx/resource.rb', line 73 def add_defaults # If there is an owner, then delegate to the owner. # Otherwise, add defaults to this object. par = owner if par and par.identifier.nil? then logger.debug { "Adding defaults to #{qp} owner #{par.qp}..." } par.add_defaults else logger.debug { "Adding defaults to #{qp} and its dependents..." } # apply the local and dependent defaults add_defaults_recursive end self end |
#add_defaults_local ⇒ Object (private)
Sets the default attribute values for this domain object. Unlike #add_defaults, this method does not set defaults for dependents. This method sets the configuration values for this domain object as described in #add_defaults, but does not set defaults for dependents.
This method is the integration point for subclasses to augment defaults with programmatic logic. If a subclass overrides this method, then it should call super before setting the local default attributes. This ensures that configuration defaults takes precedence.
685 686 687 688 |
# File 'lib/jinx/resource.rb', line 685 def add_defaults_local logger.debug { "Adding defaults to #{qp}..." } merge_attributes(self.class.defaults) end |
#add_defaults_recursive ⇒ Object (protected)
Adds the default values to this object, if necessary, and its dependents.
616 617 618 619 620 621 |
# File 'lib/jinx/resource.rb', line 616 def add_defaults_recursive # Add the local defaults. add_defaults_local # Recurse to the dependents. each_defaultable_reference { |ref| ref.add_defaults_recursive } end |
#alternate_key ⇒ Array, ...
Returns the key value or values.
200 201 202 |
# File 'lib/jinx/resource.rb', line 200 def alternate_key key_value(self.class.alternate_key_attributes) end |
#clear_attribute(attribute) ⇒ Object
Clears the given attribute value. If the current value responds to the clear
method, then the current value is cleared. Otherwise, the value is set to Metadata#empty_value.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/jinx/resource.rb', line 127 def clear_attribute(attribute) # the current value to clear current = send(attribute) return if current.nil? # call the current value clear if possible. # otherwise, set the attribute to the empty value. if current.respond_to?(:clear) then current.clear else writer = self.class.property(attribute).writer value = self.class.empty_value(attribute) send(writer, value) end end |
#content_matches?(other) ⇒ Boolean
Matches this domain object with the other domain object. The match succeeds if and only if the object classes match and for each attribute, at least one of the following conditions hold:
-
this object’s attribute value is nil or empty
-
the other object’s attribute value is nil or empty
-
if the attribute is a nondomain attribute, then the respective values are equal
-
if the attribute value is a Resource, then the value recursively matches the other value
-
if the attribute value is a Resource collection, then every item in the collection matches some item in the other collection
373 374 375 376 |
# File 'lib/jinx/resource.rb', line 373 def content_matches?(other) logger.debug { "Matching #{self} content against #{other}..." } content_matches_recursive?(other) end |
#content_matches_recursive?(other, visited = Set.new) ⇒ Boolean (protected)
Returns whether this object matches the other object on class and content.
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 |
# File 'lib/jinx/resource.rb', line 626 def content_matches_recursive?(other, visited=Set.new) return false unless self.class == other.class return false unless self.class.nondomain_attributes.all? do |pa| v = send(pa) ov = other.send(pa) if v.nil? || ov.nil? or Resource.value_equal?(v, ov) then true else logger.debug { "#{self} does not match #{other} on property #{pa}: #{v.qp} vs #{ov.qp}" } false end end self.class.domain_attributes.all? do |pa| v = send(pa) ov = other.send(pa) if v.nil_or_empty? or ov.nil_or_empty? or visited.include?(v) then true else logger.debug { "Matching #{self} #{pa} value #{v.qp} against #{other} #{pa} value #{ov.qp}..." } if Enumerable === v then v.all? do |ref| oref = ref.match_in(ov) if oref.nil? then logger.debug { "#{self} does not match #{other} on property #{pa}: #{v.pp_s} vs #{ov.pp_s}" } false else ref.content_matches_recursive?(oref, visited) end end else visited << v v.content_matches_recursive?(ov, visited) end end end end |
#copy(*attributes) ⇒ Resource
Returns a new domain object with the given attributes copied from this domain object. The attributes argument consists of either attribute Symbols or a single Enumerable consisting of Symbols. The default attributes are the Propertied#nondomain_attributes.
113 114 115 116 117 118 119 120 121 |
# File 'lib/jinx/resource.rb', line 113 def copy(*attributes) if attributes.empty? then attributes = self.class.nondomain_attributes elsif Enumerable === attributes.first then raise ArgumentError.new("#{qp} copy attributes argument is not a Symbol: #{attributes.first}") unless attributes.size == 1 attributes = attributes.first end self.class.new.merge_attributes(self, attributes) end |
#delegate_to_inverse_setter(prop, ref, writer) ⇒ Object (private)
863 864 865 866 |
# File 'lib/jinx/resource.rb', line 863 def delegate_to_inverse_setter(prop, ref, writer) logger.debug { "Setting #{qp} #{prop} by setting the #{ref.qp} inverse attribute #{prop.inverse}..." } ref.send(writer, self) end |
#dependent? ⇒ Boolean
Returns whether this domain object is dependent on another entity.
283 284 285 |
# File 'lib/jinx/resource.rb', line 283 def dependent? self.class.dependent? end |
#dependent_update_only?(other) ⇒ Boolean
Returns whether the other domain object is a dependent of this object and has an update-only non-domain attribute.
266 267 268 269 270 271 |
# File 'lib/jinx/resource.rb', line 266 def dependent_update_only?(other) other.owner == self and other.class.nondomain_attributes.detect_attribute_with_property do |prop| prop.updatable? and not prop.creatable? end end |
#dependents(properties = nil) ⇒ Enumerable
Returns this domain object’s dependents. Dependents which have an alternate preferred owner, as described in #effective_owner_property_value, are not included in the result.
299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/jinx/resource.rb', line 299 def dependents(properties=nil) properties ||= self.class.dependent_attributes.properties # Make a reference enumerator that selects only those dependents which do not have # an alternate preferred owner. ReferenceEnumerator.new(self, properties).filter do |dep| # dep is a candidate dependent. dep could have a preferred owner which differs # from self. If there is a different preferred owner, then don't call the # iteration block. oref = dep.owner oref.nil? or oref == self end end |
#difference(other, attributes = nil) ⇒ {Object => (Object,Object)} Also known as: diff
Returns the difference between this Persistable and the other Persistable for the given attributes. The default attributes are the Propertied#nondomain_attributes.
430 431 432 433 434 435 |
# File 'lib/jinx/resource.rb', line 430 def difference(other, attributes=nil) attributes ||= self.class.nondomain_attributes vh = value_hash(attributes) ovh = other.value_hash(attributes) vh.diff(ovh) { |key, v1, v2| Resource.value_equal?(v1, v2) } end |
#direct_dependents(attribute) ⇒ <Resource>
Returns the attribute references which directly depend on this owner. The default is the attribute value.
Returns an Enumerable. If the value is not already an Enumerable, then this method returns an empty array if value is nil, or a singelton array with value otherwise.
If there is more than one owner of a dependent, then subclasses should override this method to select dependents whose dependency path is shorter than an alternative dependency path, e.g. if a Node is owned by both a Graph and a parent Node. In that case, the Graph direct dependents consist of the top-level nodes owned by the Graph but not referenced by another Node.
335 336 337 338 339 340 341 342 |
# File 'lib/jinx/resource.rb', line 335 def direct_dependents(attribute) deps = send(attribute) case deps when Enumerable then deps when nil then Array::EMPTY_ARRAY else [deps] end end |
#dump {|owner| ... } ⇒ String
Prints this domain object’s content and recursively prints the referenced content. The optional selector block determines the attributes to print. The default is the Propertied#java_attributes.
532 533 534 |
# File 'lib/jinx/resource.rb', line 532 def dump(&selector) DetailPrinter.new(self, &selector).pp_s end |
#each_defaultable_reference {|dep| ... } ⇒ Object (private)
Enumerates referenced domain objects for setting defaults. This base implementation includes the #dependents. Subclasses can override this# method to add references which should be defaulted or to set the order in which defaults are applied.
752 753 754 |
# File 'lib/jinx/resource.rb', line 752 def each_defaultable_reference(&block) dependents.each(&block) end |
#effective_owner_property_value ⇒ (Property, Resource)?
Returns the (property, value) pair for which there is an owner reference, or nil if this domain object does not reference an owner.
214 215 216 217 218 219 |
# File 'lib/jinx/resource.rb', line 214 def effective_owner_property_value self.class.owner_properties.detect_value do |op| ref = send(op.attribute) [op, ref] if ref end end |
#empty_value(attribute) ⇒ Object (private)
Returns 0 if attribute is a Java primitive number, false
if attribute is a Java primitive boolean, an empty collectin if the Java attribute is a collection, nil otherwise.
872 873 874 875 876 877 878 879 |
# File 'lib/jinx/resource.rb', line 872 def empty_value(attribute) type = java_type(attribute) || return if type.primitive? then type.name == 'boolean' ? false : 0 else self.class.empty_value(attribute) end end |
#independent? ⇒ Boolean
Returns whether this domain object is not dependent on another entity.
288 289 290 |
# File 'lib/jinx/resource.rb', line 288 def independent? not dependent? end |
#java_type(attribute) ⇒ Object (private)
Returns the Java type of the given attribute, or nil if attribute is not a Java property attribute.
882 883 884 885 |
# File 'lib/jinx/resource.rb', line 882 def java_type(attribute) prop = self.class.property(attribute) prop.property_descriptor.attribute_type if JavaProperty === prop end |
#key(attributes = nil) ⇒ Array, ...
Returns the first non-nil #key_value for the primary, secondary and alternate key attributes.
163 164 165 |
# File 'lib/jinx/resource.rb', line 163 def key(attributes=nil) primary_key or secondary_key or alternate_key end |
#key_value(attributes) ⇒ Array, ...
Returns the key for the given key attributes as follows:
-
If there are no key attributes, then nil.
-
Otherwise, if any key attribute value is missing, then nil.
-
Otherwise, if the key attributes is a singleton Array, then the key is the value of the sole key attribute.
-
Otherwise, the key is an Array of the key attribute values.
176 177 178 179 180 181 182 183 184 185 |
# File 'lib/jinx/resource.rb', line 176 def key_value(attributes) attributes ||= self.class.primary_key_attributes case attributes.size when 0 then nil when 1 then send(attributes.first) else key = attributes.map { |pa| send(pa) || return } key unless key.empty? end end |
#mandatory_attributes ⇒ <Symbol>
Returns the attributes which are required for save. This base implementation returns the class Propertied#mandatory_attributes. Subclasses can override this method for domain object state-specific refinements.
317 318 319 |
# File 'lib/jinx/resource.rb', line 317 def mandatory_attributes self.class.mandatory_attributes end |
#match_attribute_value(prop, newval, oldval) {|sources, targets| ... } ⇒ {Resource => Resource} (private)
Returns the source => target hash of matches for the given prop newval sources and oldval targets. If the matcher block is given, then that block is called on the sources and targets. Otherwise, match_all is called.
898 899 900 901 902 903 904 905 906 907 908 909 910 |
# File 'lib/jinx/resource.rb', line 898 def match_attribute_value(prop, newval, oldval) # make Enumerable targets and sources for matching sources = newval.to_enum targets = oldval.to_enum # match sources to targets unless oldval.nil_or_empty? then logger.debug { "Matching source #{newval.qp} to target #{qp} #{prop} #{oldval.qp}..." } end matches = block_given? ? yield(sources, targets) : Resource.match_all(sources, targets) logger.debug { "Matched #{qp} #{prop}: #{matches.qp}." } unless matches.empty? matches end |
#match_in(others) ⇒ Resource?
Matches this dependent domain object with the others on type and key attributes in the scope of a parent object. Returns the object in others which matches this domain object, or nil if none.
The match attributes are, in order:
-
the primary key
-
the secondary key
-
the alternate key
This domain object is matched against the others on the above attributes in succession until a unique match is found. The key attribute matches are strict, i.e. each key attribute value must be non-nil and match the other value.
393 394 395 396 397 398 399 400 401 402 403 404 |
# File 'lib/jinx/resource.rb', line 393 def match_in(others) # trivial case: self is in others return self if others.include?(self) # filter for the same type unless others.all? { |other| self.class === other } then others = others.filter { |other| self.class === other } end # match on primary, secondary or alternate key match_unique_object_with_attributes(others, self.class.primary_key_attributes) or match_unique_object_with_attributes(others, self.class.secondary_key_attributes) or match_unique_object_with_attributes(others, self.class.alternate_key_attributes) end |
#match_in_owner_scope(others) ⇒ Resource?
Returns the match of this domain object in the scope of a matching owner as follows:
-
If #match_in returns a match, then that match is the result is used.
-
Otherwise, if this is a dependent attribute then the match is attempted on a secondary key without owner attributes. Defaults are added to this object in order to pick up potential secondary key values.
414 415 416 |
# File 'lib/jinx/resource.rb', line 414 def match_in_owner_scope(others) match_in(others) or others.detect { |other| matches_without_owner_attribute?(other) } end |
#match_unique_object_with_attributes(others, attributes) ⇒ Object (private)
Returns the object in others which uniquely matches this domain object on the given attributes, or nil if there is no unique match. This method returns nil if any attributes value is nil.
930 931 932 933 934 935 936 937 938 |
# File 'lib/jinx/resource.rb', line 930 def match_unique_object_with_attributes(others, attributes) vh = value_hash(attributes) return if vh.empty? or vh.size < attributes.size matches = others.select do |other| self.class == other.class and vh.all? { |pa, v| other.matches_attribute_value?(pa, v) } end matches.first if matches.size == 1 end |
#matches?(other) ⇒ Boolean
Returns whether this object matches the fetched other object on class and a primary, secondary or alternate key.
347 348 349 350 351 352 353 354 355 356 |
# File 'lib/jinx/resource.rb', line 347 def matches?(other) # trivial case return true if equal?(other) # check the type return false unless self.class == other.class # match on primary, secondary or alternate key matches_key_attributes?(other, self.class.primary_key_attributes) or matches_key_attributes?(other, self.class.secondary_key_attributes) or matches_key_attributes?(other, self.class.alternate_key_attributes) end |
#matches_attribute_value?(attribute, value) ⇒ Boolean (protected)
Returns whether this Resource’s attribute value matches the given value. A domain attribute match is determined by #match?. A non-domain attribute match is determined by an equality comparison.
603 604 605 606 |
# File 'lib/jinx/resource.rb', line 603 def matches_attribute_value?(attribute, value) v = send(attribute) Resource === v ? value.matches?(v) : value == v end |
#matches_key_attributes?(other, attributes) ⇒ Boolean (private)
Returns whether there is a non-nil value for each attribute and the value matches the other attribute value.
915 916 917 918 919 920 921 922 923 924 925 926 |
# File 'lib/jinx/resource.rb', line 915 def matches_key_attributes?(other, attributes) return false if attributes.empty? attributes.all? do |pa| v = send(pa) if v.nil? then false else ov = other.send(pa) Resource === v ? v.matches?(ov) : v == ov end end end |
#matches_without_owner_attribute?(other) ⇒ Boolean (private)
Returns whether this domain object matches the other domain object as follows:
-
The classes are the same.
-
There are not conflicting primary key values.
-
Each non-owner secondary key value matches.
Note that objects without a secondary key match.
845 846 847 848 849 850 851 852 853 854 855 856 857 858 |
# File 'lib/jinx/resource.rb', line 845 def matches_without_owner_attribute?(other) return false unless other.class == self.class # check the primary key return false unless self.class.primary_key_attributes.all? do |ka| kv = send(ka) okv = other.send(ka) kv.nil? or okv.nil? or kv == okv end # match on the non-owner secondary key oas = self.class.owner_attributes self.class.secondary_key_attributes.all? do |ka| oas.include?(ka) or other.matches_attribute_value?(ka, send(ka)) end end |
#minimal_match?(other) ⇒ Boolean
Returns the domain object in others which matches this dependent domain object within the scope of a parent on a minimally acceptable constraint. This method is used when this object might be partially complete–say, lacking a secondary key value–but is expected to match one of the others, e.g. when matching a referenced object to its fetched counterpart.
This base implementation returns whether the following conditions hold:
-
other is the same class as this domain object
-
if both identifiers are non-nil, then they are equal
Subclasses can override this method to impose additional minimal consistency constraints.
453 454 455 456 |
# File 'lib/jinx/resource.rb', line 453 def minimal_match?(other) self.class === other and (identifier.nil? or other.identifier.nil? or identifier == other.identifier) end |
#missing_mandatory_attributes ⇒ <Symbol> (protected)
Returns the required attributes for this domain object which are nil or empty.
609 610 611 |
# File 'lib/jinx/resource.rb', line 609 def missing_mandatory_attributes mandatory_attributes.select { |pa| send(pa).nil_or_empty? } end |
#non_id_search_attribute_values ⇒ Object (private)
Returns the attribute => value hash to use for matching this domain object.
953 954 955 956 957 958 959 960 961 962 |
# File 'lib/jinx/resource.rb', line 953 def non_id_search_attribute_values # if there is a secondary key, then search on those attributes. # otherwise, search on all attributes. key_props = self.class.secondary_key_attributes pas = key_props.empty? ? self.class.nondomain_java_attributes : key_props # associate the values attr_values = pas.to_compact_hash { |pa| send(pa) } # if there is no secondary key, then cull empty values key_props.empty? ? attr_values.delete_if { |pa, value| value.nil? } : attr_values end |
#owner ⇒ Resource?
Returns the domain object that owns this object, or nil if this object is not dependent on an owner.
206 207 208 |
# File 'lib/jinx/resource.rb', line 206 def owner self.class.owner_attributes.detect_value { |pa| send(pa) } end |
#owner=(obj) ⇒ Object
Sets this dependent’s owner attribute to the given domain object.
226 227 228 229 230 231 232 233 234 |
# File 'lib/jinx/resource.rb', line 226 def owner=(obj) if obj.nil? then op, ov = effective_owner_property_value || return else op = self.class.owner_properties.detect { |prop| prop.type === obj } end if op.nil? then raise NoMethodError.new("#{self.class.qp} does not have an owner attribute for #{obj}") end set_property_value(op.attribute, obj) end |
#owner_ancestor?(other) ⇒ Boolean
Returns whether the other domain object is this object’s #owner or an #owner_ancestor? of this object’s #owner.
239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/jinx/resource.rb', line 239 def owner_ancestor?(other) ownr = self.owner if ownr then ownr == other or ownr.owner_ancestor?(other) elsif self.class.owner_types.size == self.class.owner_properties.size then false else path = other.class.dependency_path_to(self.class) !!path and other.reachable?(self, path) end end |
#path_value(path) ⇒ Object
Returns the value for the given attribute path Array or String expression, e.g.:
study.path_value("site.address.state")
follows the study
-> site
-> address
-> state
accessors and returns the state
value, or nil if any intermediate reference is nil. The array form for the above example is:
study.path_value([:site, :address, :state])
478 479 480 481 482 483 484 485 |
# File 'lib/jinx/resource.rb', line 478 def path_value(path) path = path.split('.').map { |pa| pa.to_sym } if String === path path.inject(self) do |parent, pa| value = parent.send(pa) return if value.nil? value end end |
#post_initialize ⇒ Object
Initialization hook. This default implementation is a no-op.
56 57 |
# File 'lib/jinx/resource.rb', line 56 def post_initialize end |
#pretty_print(q) ⇒ String
Returns the formatted content of this Resource.
519 520 521 522 523 |
# File 'lib/jinx/resource.rb', line 519 def pretty_print(q) q.text(qp) content = printable_content q.pp_hash(content) unless content.empty? end |
#primary_key ⇒ Array, ...
Returns the key value or values.
188 189 190 |
# File 'lib/jinx/resource.rb', line 188 def primary_key key_value(self.class.primary_key_attributes) end |
#print_class_and_id ⇒ Object Also known as: qp
Prints this object’s class demodulized name and object id.
60 61 62 |
# File 'lib/jinx/resource.rb', line 60 def print_class_and_id "#{self.class.qp}@#{proxy_object_id}" end |
#printable_content(attributes = nil) {|ref| ... } ⇒ {Symbol => String}
Returns this domain object’s attributes content as an attribute => value hash suitable for printing.
The default attributes are this object’s saved attributes. The optional reference_printer is used to print a referenced domain object.
560 561 562 563 564 |
# File 'lib/jinx/resource.rb', line 560 def printable_content(attributes=nil, &reference_printer) attributes ||= printworthy_attributes vh = value_hash(attributes) vh.transform_value { |value| printable_value(value, &reference_printer) } end |
#printable_value(value, &reference_printer) ⇒ Object (private)
Returns a value suitable for printing. If value is a domain object, then the block provided to this method is called. The default block creates a new ReferencePrinter on the value.
807 808 809 810 811 812 813 814 815 |
# File 'lib/jinx/resource.rb', line 807 def printable_value(value, &reference_printer) Jinx::Collector.on(value) do |item| if Resource === item then block_given? ? yield(item) : printable_value(item) { |ref| ReferencePrinter.new(ref) } else item end end end |
#printworthy_attributes ⇒ <Symbol] the attributes to print (private)
Returns an attribute => value hash which identifies the object. If this object has a complete primary key, than the primary key attributes are returned. Otherwise, if there are secondary key attributes, then they are returned. Otherwise, if there are nondomain attributes, then they are returned. Otherwise, if there are fetched attributes, then they are returned.
824 825 826 827 828 829 830 831 832 833 834 |
# File 'lib/jinx/resource.rb', line 824 def printworthy_attributes if self.class.primary_key_attributes.all? { |pa| !!send(pa) } then self.class.primary_key_attributes elsif not self.class.secondary_key_attributes.empty? then self.class.secondary_key_attributes elsif not self.class.nondomain_java_attributes.empty? then self.class.nondomain_java_attributes else self.class.fetched_attributes end end |
#proxy_object_id ⇒ Integer
Returns the object id.
47 48 49 50 |
# File 'lib/jinx/resource.rb', line 47 def proxy_object_id # make a hash code on demand @_hc ||= (Object.new.object_id * 31) + 17 end |
#reachable?(other, path) ⇒ Boolean
Returns whether following there is a path from this object through the given properties to the other object.
255 256 257 258 259 260 261 |
# File 'lib/jinx/resource.rb', line 255 def reachable?(other, path) return false if path.empty? prop = path.shift send(prop.attribute).to_enum.any? do |ref| ref == other or ref.reachable?(other, path) end end |
#reference_hierarchy {|ref| ... } ⇒ Enumerable
Returns an enumerator on the transitive closure of the reference attributes. If a block is given to this method, then the block called on each reference determines which attributes to visit. Otherwise, all saved references are visited.
465 466 467 |
# File 'lib/jinx/resource.rb', line 465 def reference_hierarchy ReferenceVisitor.new { |ref| yield ref }.to_enum(self) end |
#references(attributes = nil) ⇒ <Resource>
Returns the domain object references for the given attributes.
277 278 279 280 |
# File 'lib/jinx/resource.rb', line 277 def references(attributes=nil) attributes ||= self.class.domain_attributes attributes.map { |pa| send(pa) }.flatten.compact end |
#search_attribute_values ⇒ Object (private)
Returns the attribute => value hash to use for matching this domain object as follows:
-
If this domain object has a database identifier, then the identifier is the sole match criterion attribute.
-
Otherwise, if a secondary key is defined for the object’s class, then those attributes are used.
-
Otherwise, all attributes are used.
If any secondary key value is nil, then this method returns an empty hash, since the search is ambiguous.
946 947 948 949 |
# File 'lib/jinx/resource.rb', line 946 def search_attribute_values # if this object has a database identifier, then the identifier is the search criterion identifier.nil? ? non_id_search_attribute_values : { :identifier => identifier } end |
#secondary_key ⇒ Array, ...
Returns the key value or values.
194 195 196 |
# File 'lib/jinx/resource.rb', line 194 def secondary_key key_value(self.class.secondary_key_attributes) end |
#set_property_value(prop, value) ⇒ Object
Sets this domain object’s attribute to the value. This method clears the current attribute value, if any, and merges the new value. Merge rather than assignment ensures that a collection type is preserved, e.g. an Array value is assigned to a set domain type by first clearing the set and then merging the array content into the set.
149 150 151 152 153 154 155 156 157 |
# File 'lib/jinx/resource.rb', line 149 def set_property_value(prop, value) prop = self.class.property(prop) if Symbol === prop if prop.domain? and prop.collection? then clear_attribute(prop.attribute) merge_attribute(prop.attribute, value) else set_typed_property_value(prop, value) end end |
#set_typed_property_value(property, value) ⇒ Object (private)
737 738 739 740 741 742 743 744 |
# File 'lib/jinx/resource.rb', line 737 def set_typed_property_value(property, value) begin send(property.writer, value) rescue TypeError logger.error("Cannot set #{self.class.qp} #{property} to #{value.qp} - " + $!) raise end end |
#to_s(attributes = nil) ⇒ String Also known as: inspect
Prints this domain object in the format:
class_name@object_id{attribute => value ...}
The default attributes include identifying attributes.
542 543 544 545 546 |
# File 'lib/jinx/resource.rb', line 542 def to_s(attributes=nil) content = printable_content(attributes) content_s = content.pp_s(:single_line) unless content.empty? "#{print_class_and_id}#{content_s}" end |
#validate ⇒ Resource
Validates this domain object and its ##dependents for consistency and completeness. An object is valid if it contains a non-nil value for each mandatory attribute. Objects which have already been validated are skipped.
A Resource class should not override this method, but override the private #validate_local method instead.
97 98 99 100 101 102 103 104 |
# File 'lib/jinx/resource.rb', line 97 def validate unless @validated then validate_local @validated = true end dependents.each { |dep| dep.validate } self end |
#validate_local ⇒ Object (private)
Validates that this domain object is internally consistent. Subclasses override this method for additional validation, but should call super first.
695 696 697 698 |
# File 'lib/jinx/resource.rb', line 695 def validate_local validate_mandatory_attributes validate_owner end |
#validate_mandatory_attributes ⇒ Object (private)
Validates that this domain object contains a non-nil value for each mandatory attribute.
703 704 705 706 707 708 709 710 |
# File 'lib/jinx/resource.rb', line 703 def validate_mandatory_attributes invalid = missing_mandatory_attributes unless invalid.empty? then logger.error("Validation of #{qp} unsuccessful - missing #{invalid.join(', ')}:\n#{dump}") raise ValidationError.new("Required attribute value missing for #{self}: #{invalid.join(', ')}") end validate_owner end |
#validate_owner ⇒ Object (private)
Validates that this domain object either doesn’t have an owner attribute or has a unique effective owner.
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 |
# File 'lib/jinx/resource.rb', line 717 def validate_owner # If there is an unambigous owner, then we are done. return unless owner.nil? # If there is more than one owner attribute, then check that there is at most one # unambiguous owner reference. The owner method returns nil if the owner is ambiguous. if self.class.owner_attributes.size > 1 then vh = value_hash(self.class.owner_attributes) if vh.size > 1 then raise ValidationError.new("Dependent #{self} references multiple owners #{vh.pp_s}:\n#{dump}") end end # If there is an owner reference attribute, then there must be an owner. if self.class.bidirectional_java_dependent? then raise ValidationError.new("Dependent #{self} does not reference an owner") end end |
#visit_dependents {|dep| ... } ⇒ Object
Applies the operator block to the transitive closure of this domain object’s dependency relation. The block argument is a dependent.
504 505 506 |
# File 'lib/jinx/resource.rb', line 504 def visit_dependents(&operator) DEPENDENT_VISITOR.visit(self, &operator) end |
#visit_owners {|dep| ... } ⇒ Object
Applies the operator block to the transitive closure of this domain object’s owner relation.
512 513 514 515 |
# File 'lib/jinx/resource.rb', line 512 def visit_owners(&operator) # :yields: owner ref = owner yield(ref) and ref.visit_owners(&operator) if ref end |
#visit_path(*path) {|attribute| ... } ⇒ Object
Applies the operator block to this object and each domain object in the reference path. This method visits the transitive closure of each recursive path attribute.
494 495 496 497 |
# File 'lib/jinx/resource.rb', line 494 def visit_path(*path, &operator) visitor = ReferencePathVisitor.new(self.class, path) visitor.visit(self, &operator) end |