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.
-
#diff(other, attributes = nil) ⇒ {Object => (Object,Object)}
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)?
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=(owner) ⇒ 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. -
#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.
-
#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(attribute, 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.
-
#value_hash(attributes = nil) ⇒ {Symbol => Object}
Returns an attribute => value hash for the specified attributes with a non-nil, non-empty value.
-
#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_attribute, #merge_attributes, #merge_domain_property_value, #merge_nondomain_property_value, #mergeable__equal?
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)
726 727 728 |
# File 'lib/jinx/resource.rb', line 726 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.
392 393 394 |
# File 'lib/jinx/resource.rb', line 392 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.
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 |
# File 'lib/jinx/resource.rb', line 546 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.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/jinx/resource.rb', line 63 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.
655 656 657 658 |
# File 'lib/jinx/resource.rb', line 655 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.
586 587 588 589 590 591 |
# File 'lib/jinx/resource.rb', line 586 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.
189 190 191 |
# File 'lib/jinx/resource.rb', line 189 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.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/jinx/resource.rb', line 117 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
345 346 347 348 |
# File 'lib/jinx/resource.rb', line 345 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.
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 |
# File 'lib/jinx/resource.rb', line 596 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.
103 104 105 106 107 108 109 110 111 |
# File 'lib/jinx/resource.rb', line 103 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)
833 834 835 836 |
# File 'lib/jinx/resource.rb', line 833 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.
255 256 257 |
# File 'lib/jinx/resource.rb', line 255 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.
229 230 231 232 |
# File 'lib/jinx/resource.rb', line 229 def dependent_update_only?(other) other.owner == self and other.class.nondomain_attributes.detect_with_property { |prop| prop.updatable? and not prop.creatable? } 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.
271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/jinx/resource.rb', line 271 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 |
#diff(other, attributes = nil) ⇒ {Object => (Object,Object)}
Returns the difference between this Persistable and the other Persistable for the given attributes. The default attributes are the Propertied#nondomain_attributes.
402 403 404 405 406 407 |
# File 'lib/jinx/resource.rb', line 402 def diff(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.
307 308 309 310 311 312 313 314 |
# File 'lib/jinx/resource.rb', line 307 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.
502 503 504 |
# File 'lib/jinx/resource.rb', line 502 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.
722 723 724 |
# File 'lib/jinx/resource.rb', line 722 def each_defaultable_reference(&block) dependents.each(&block) end |
#effective_owner_property_value ⇒ (Property, Resource)?
owner reference, or nil if this domain object does not reference an owner
201 202 203 204 205 206 |
# File 'lib/jinx/resource.rb', line 201 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.
842 843 844 845 846 847 848 849 |
# File 'lib/jinx/resource.rb', line 842 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.
260 261 262 |
# File 'lib/jinx/resource.rb', line 260 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.
852 853 854 855 |
# File 'lib/jinx/resource.rb', line 852 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.
152 153 154 |
# File 'lib/jinx/resource.rb', line 152 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.
165 166 167 168 169 170 171 172 173 174 |
# File 'lib/jinx/resource.rb', line 165 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.
289 290 291 |
# File 'lib/jinx/resource.rb', line 289 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.
868 869 870 871 872 873 874 875 876 877 878 879 880 |
# File 'lib/jinx/resource.rb', line 868 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.
365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'lib/jinx/resource.rb', line 365 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.
386 387 388 |
# File 'lib/jinx/resource.rb', line 386 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.
900 901 902 903 904 905 906 907 908 |
# File 'lib/jinx/resource.rb', line 900 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.
319 320 321 322 323 324 325 326 327 328 |
# File 'lib/jinx/resource.rb', line 319 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.
573 574 575 576 |
# File 'lib/jinx/resource.rb', line 573 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.
885 886 887 888 889 890 891 892 893 894 895 896 |
# File 'lib/jinx/resource.rb', line 885 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.
815 816 817 818 819 820 821 822 823 824 825 826 827 828 |
# File 'lib/jinx/resource.rb', line 815 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.
423 424 425 426 |
# File 'lib/jinx/resource.rb', line 423 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.
579 580 581 |
# File 'lib/jinx/resource.rb', line 579 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.
923 924 925 926 927 928 929 930 931 932 |
# File 'lib/jinx/resource.rb', line 923 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.
195 196 197 |
# File 'lib/jinx/resource.rb', line 195 def owner self.class.owner_attributes.detect_value { |pa| send(pa) } end |
#owner=(owner) ⇒ Object
Sets this dependent’s owner attribute to the given domain object.
212 213 214 215 216 |
# File 'lib/jinx/resource.rb', line 212 def owner=(owner) pa = self.class.owner_attribute if pa.nil? then raise NoMethodError.new("#{self.class.qp} does not have a unique owner attribute") end set_property_value(pa, owner) 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.
221 222 223 224 |
# File 'lib/jinx/resource.rb', line 221 def owner_ancestor?(other) ownr = self.owner ownr and (ownr == other or ownr.owner_ancestor?(other)) 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])
448 449 450 451 452 453 454 455 |
# File 'lib/jinx/resource.rb', line 448 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 |
#pretty_print(q) ⇒ String
Returns the formatted content of this Resource.
489 490 491 492 493 |
# File 'lib/jinx/resource.rb', line 489 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.
177 178 179 |
# File 'lib/jinx/resource.rb', line 177 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.
50 51 52 |
# File 'lib/jinx/resource.rb', line 50 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.
530 531 532 533 534 |
# File 'lib/jinx/resource.rb', line 530 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.
777 778 779 780 781 782 783 784 785 |
# File 'lib/jinx/resource.rb', line 777 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.
794 795 796 797 798 799 800 801 802 803 804 |
# File 'lib/jinx/resource.rb', line 794 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.
44 45 46 47 |
# File 'lib/jinx/resource.rb', line 44 def proxy_object_id # make a hash code on demand @_hc ||= (Object.new.object_id * 31) + 17 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.
435 436 437 |
# File 'lib/jinx/resource.rb', line 435 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.
249 250 251 252 |
# File 'lib/jinx/resource.rb', line 249 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.
916 917 918 919 |
# File 'lib/jinx/resource.rb', line 916 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.
183 184 185 |
# File 'lib/jinx/resource.rb', line 183 def secondary_key key_value(self.class.secondary_key_attributes) end |
#set_property_value(attribute, 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.
138 139 140 141 142 143 144 145 146 |
# File 'lib/jinx/resource.rb', line 138 def set_property_value(attribute, value) prop = self.class.property(attribute) if prop.domain? and prop.collection? then clear_attribute(attribute) merge_attribute(attribute, value) else set_typed_property_value(prop, value) end end |
#set_typed_property_value(property, value) ⇒ Object (private)
707 708 709 710 711 712 713 714 |
# File 'lib/jinx/resource.rb', line 707 def set_typed_property_value(property, value) begin send(property.writer, value) rescue TypeError # Add the attribute to the error message. raise TypeError.new("Cannot set #{self.class.qp} #{property} to #{value.qp} - " + $!) 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.
512 513 514 515 516 |
# File 'lib/jinx/resource.rb', line 512 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.
87 88 89 90 91 92 93 94 |
# File 'lib/jinx/resource.rb', line 87 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.
665 666 667 668 |
# File 'lib/jinx/resource.rb', line 665 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.
673 674 675 676 677 678 679 680 |
# File 'lib/jinx/resource.rb', line 673 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.
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 |
# File 'lib/jinx/resource.rb', line 687 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_dependent? then raise ValidationError.new("Dependent #{self} does not reference an owner") end end |
#value_hash(attributes = nil) ⇒ {Symbol => Object}
Returns an attribute => value hash for the specified attributes with a non-nil, non-empty value. The default attributes are this domain object’s class Propertied#attributes. Only non-nil attributes defined by this Resource are included in the result hash.
240 241 242 243 |
# File 'lib/jinx/resource.rb', line 240 def value_hash(attributes=nil) attributes ||= self.class.attributes attributes.to_compact_hash { |pa| send(pa) if self.class.method_defined?(pa) } 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.
474 475 476 |
# File 'lib/jinx/resource.rb', line 474 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.
482 483 484 485 |
# File 'lib/jinx/resource.rb', line 482 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.
464 465 466 467 |
# File 'lib/jinx/resource.rb', line 464 def visit_path(*path, &operator) visitor = ReferencePathVisitor.new(self.class, path) visitor.visit(self, &operator) end |