Module: ActiveFacts::API::ObjectType

Included in:
Instance::ClassMethods
Defined in:
lib/activefacts/api/object_type.rb

Overview

ObjectType contains methods that are added as class methods to all Value and Entity classes.

Constant Summary collapse

SKIP_MUTUAL_PROPAGATION =
0x1
CHECKED_IDENTIFYING_ROLE =
0x2

Instance Method Summary collapse

Instance Method Details

#add_role(role) ⇒ Object



52
53
54
55
# File 'lib/activefacts/api/object_type.rb', line 52

def add_role(role)
  all_role[role.name] = role
  @all_role_transitive = nil  # Undo the caching
end

#all_role(role_name = nil) ⇒ Object

Each ObjectType maintains a list of the Roles it plays:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/activefacts/api/object_type.rb', line 23

def all_role(role_name = nil)
  unless instance_variable_defined?(@@all_role_name ||= "@all_role")  # Avoid "instance variable not defined" warning from ||=
    @all_role = RoleCollection.new
  end
  case role_name
  when nil
    @all_role
  when Symbol, String
    # Search this class then all supertypes:
    unless role = @all_role[role_name.to_sym]
      role = nil
      supertypes.each do |supertype|
          begin
            role = supertype.all_role(role_name)
          rescue RoleNotDefinedException
            next
          end
          break
        end
    end
    unless role
      raise RoleNotDefinedException.new(self, role_name)
    end
    role
  else
    nil
  end
end

#all_role_transitiveObject



57
58
59
60
61
62
63
64
# File 'lib/activefacts/api/object_type.rb', line 57

def all_role_transitive
  return @all_role_transitive if @all_role_transitive
  @all_role_transitive = all_role.dup
  supertypes_transitive.each do |klass|
    @all_role_transitive.merge!(klass.all_role)
  end
  @all_role_transitive
end

#check_identifying_role_has_valid_cardinality(type, role) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/activefacts/api/object_type.rb', line 108

def check_identifying_role_has_valid_cardinality(type, role)
  if is_entity_type && identifying_role_names.include?(role)
    case type
    when :has_one
      if identifying_role_names.size == 1
        raise InvalidIdentificationException.new(self, role, true)
      end
    when :one_to_one
      if identifying_role_names.size > 1
        raise InvalidIdentificationException.new(self, role, false)
      end
    end
  end
end

#has_one(role_name, options = {}) ⇒ Object

Define a binary fact type relating this object_type to another, with a uniqueness constraint only on this object_type’s role. This method creates two accessor methods, one in this object_type and one in the other object_type.

  • role_name is a Symbol for the name of the role (this end of the relationship)

Options contain optional keys:

  • :class - A class name, Symbol or String naming a class, required if it doesn’t match the role_name. Use a symbol or string if the class isn’t defined yet, and the methods will be created later, when the class is first defined.

  • :mandatory - if this role may not be NULL in a valid fact population, say :mandatory => true. Mandatory constraints are only enforced during validation (e.g. before saving).

  • :counterpart - use if the role at the other end should have a name other than the default :all_<object_type> or :all_<object_type>_as_<role_name>

  • :reading - for verbalisation. Not used yet.

  • :restrict - a list of values or ranges which this role may take. Not used yet.



85
86
87
88
89
# File 'lib/activefacts/api/object_type.rb', line 85

def has_one(role_name, options = {})
  role_name, related, mandatory, related_role_name, restrict = extract_binary_params(role_name, false, options)
  check_identifying_role_has_valid_cardinality(:has_one, role_name)
  define_binary_fact_type(false, role_name, related, mandatory, related_role_name, restrict)
end

#maybe(role_name, options = {}) ⇒ Object

Define a unary fact type attached to this object_type; in essence, a boolean attribute.

Example: maybe :is_ceo



69
70
71
72
73
# File 'lib/activefacts/api/object_type.rb', line 69

def maybe(role_name, options = {})
  raise UnrecognisedOptionsException.new("role", role_name, options.keys) unless options.empty?
  fact_type = FactType.new
  realise_role(Role.new(fact_type, self, role_name, false, true))
end

#one_to_one(role_name, options = {}) ⇒ Object

Define a binary fact type joining this object_type to another, with uniqueness constraints in both directions, i.e. a one-to-one relationship This method creates two accessor methods, one in this object_type and one in the other object_type.

  • role_name is a Symbol for the name of the role (this end of the relationship)

Options contain optional keys:

  • :class - A class name, Symbol or String naming a class, required if it doesn’t match the role_name. Use a symbol or string if the class isn’t defined yet, and the methods will be created later, when the class is first defined.

  • :mandatory - if this role may not be NULL in a valid fact population, say :mandatory => true. Mandatory constraints are only enforced during validation (e.g. before saving).

  • :counterpart - use if the role at the other end should have a name other than the default :all_<object_type> or :all_<object_type>_as_<role_name>

  • :reading - for verbalisation. Not used yet.

  • :restrict - a list of values or ranges which this role may take. Not used yet.



101
102
103
104
105
106
# File 'lib/activefacts/api/object_type.rb', line 101

def one_to_one(role_name, options = {})
  role_name, related, mandatory, related_role_name, restrict =
    extract_binary_params(role_name, true, options)
  check_identifying_role_has_valid_cardinality(:one_to_one, role_name)
  define_binary_fact_type(true, role_name, related, mandatory, related_role_name, restrict)
end

#realise_role(role) ⇒ Object

Every new role added or inherited comes through here:



183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/activefacts/api/object_type.rb', line 183

def realise_role(role) #:nodoc:
  if (role.unary?)
    # Unary role
    define_unary_role_accessor(role)
  elsif (role.unique)
    if role.counterpart.unique
      define_one_to_one_accessor(role)
    else
      define_one_to_many_accessor(role)
    end
  else
    define_many_to_one_accessor(role)
  end
end

#subtypesObject



174
175
176
# File 'lib/activefacts/api/object_type.rb', line 174

def subtypes
  @subtypes ||= []
end

#subtypes_transitiveObject



178
179
180
# File 'lib/activefacts/api/object_type.rb', line 178

def subtypes_transitive
  (subtypes+subtypes.map(&:subtypes_transitive)).flatten.uniq
end

#supertypes(*object_types) ⇒ Object

Access supertypes or add new supertypes; multiple inheritance. With parameters (Class objects), it adds new supertypes to this class. Instances of this class will then have role methods for any new superclasses (transitively). Superclasses must be Ruby classes which are existing ObjectTypes. Without parameters, it returns the array of ObjectType supertypes (one by Ruby inheritance, any others as defined using this method)



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/activefacts/api/object_type.rb', line 129

def supertypes(*object_types)
  @supertypes ||= []
  all_supertypes = supertypes_transitive
  object_types.each do |object_type|
    next if all_supertypes.include? object_type
    supertype =
      case object_type
      when Class
        object_type
      when Symbol
        # No late binding here:
        (object_type = vocabulary.const_get(object_type.to_s.camelcase))
      else
        raise InvalidSupertypeException.new("Illegal supertype #{object_type.inspect} for #{self.class.basename}")
      end
    unless supertype.respond_to?(:vocabulary) and supertype.vocabulary == self.vocabulary
      raise InvalidSupertypeException.new("#{supertype.name} must be an object type in #{vocabulary.name}")
    end

    if is_entity_type != supertype.is_entity_type
      raise InvalidSupertypeException.new("#{self} < #{supertype}: A value type may not be a supertype of an entity type, and vice versa")
    end

    TypeInheritanceFactType.new(supertype, self)
    @supertypes << supertype

    # Realise the roles (create accessors) of this supertype.
    realise_supertypes(object_type, all_supertypes)
  end
  [(superclass.respond_to?(:vocabulary) ? superclass : nil), *@supertypes].compact
end

#supertypes_transitiveObject

Return the array of all ObjectType supertypes, transitively.



162
163
164
165
166
167
168
169
170
171
172
# File 'lib/activefacts/api/object_type.rb', line 162

def supertypes_transitive
  supertypes = []
  v = superclass.respond_to?(:vocabulary) ? superclass.vocabulary : nil
  supertypes << superclass if v.kind_of?(Module)
  supertypes += (@supertypes ||= [])
  sts = supertypes.inject([]) do |a, t|
    next if a.include?(t)
    a += [t] + t.supertypes_transitive
  end.uniq
  sts # The local variable unconfuses rcov
end

#vocabularyObject

What vocabulary (Ruby module) does this object_type belong to?



18
19
20
# File 'lib/activefacts/api/object_type.rb', line 18

def vocabulary
  modspace        # The module that contains this object_type.
end