class Athena::ORM::Proxy(T)
inherits Athena::ORM::Entity
#
Lazy ToOne wrapper for owning-side associations.
By default, loading an entity also loads the entity each of its AORMA::OneToOne and AORMA::ManyToOne fields points to.
Typing an owning-side field as AORM::Proxy(Target)? instead of Target? makes it lazy: the field holds a proxy that only knows the target's identifier, and loads the target the first time it's needed.
@[AORMA::Entity]
class User < AORM::Entity
# ...
@[AORMA::OneToOne]
property avatar : AORM::Proxy(Avatar)? = nil
end
user = em.find! User, 1 # Doesn't query the user's avatar
avatar = user.avatar.not_nil!
avatar.loaded? # => false
avatar.id # => 10, still not loaded
avatar.url # => "https://example.com/avatar.png", loads the avatar
avatar.loaded? # => true
The target entity's methods can be called on the proxy directly; any method the proxy doesn't define itself is forwarded to #inner, which loads the target on first use.
#id is the exception: when the target's identifier field is named id, it's read from the proxy without loading anything.
Since the field holds a proxy, raw entity assignment (user.avatar = some_avatar) requires an explicit wrap: user.avatar = AORM::Proxy(Avatar).wrap(some_avatar).
The wrapped entity is persisted, and cascades, like one assigned to a plain Target? field.
Identity#
The proxy is a separate object from the entity it loads.
Methods every object has, such as ==, hash, same? and is_a?, act on the proxy itself rather than being forwarded.
Code that needs the raw Target reference (to pass to a function whose signature expects it, or to do an is_a? check against a subclass) calls #inner explicitly.
Object identity is NOT preserved across a load: once the proxy loads, AORM::EntityManager#find returns the loaded Target rather than the proxy, so proxy.same?(em.find(Avatar, proxy.id)) is false.
Stale proxy references stay functional, delegating to the loaded entity.
Note
Owning-side ToOne only.
Fields mapped with mapped_by are always loaded along with their owner, and typing one as a proxy raises when the entity's metadata is built.
Todo
Fields typed as a plain Target? load each missing target with its own query once the owner's query has finished, rather than batching them.
Constructors#
.wrap(value : T) : self#
Wraps an already-loaded entity.
The resulting proxy is loaded? from the start and never issues a SELECT.
user.avatar = AORM::Proxy(Avatar).wrap avatar
Class methods#
.wrap(value : Proxy(T)) : Proxy(T)#
Idempotent: passing through an existing proxy returns it unchanged.
Methods#
#id#
Allows reading the #id of a proxy without triggering a load.
This only applies to an identifier field named id; if the target has no such field, this raises.
Reading an identifier field with any other name, such as proxy.code, loads the target like any other forwarded method.
#inner : T#
Returns the target entity, loading it on the first call.
Raises if the target no longer exists in the database.
#inspect(io : IO) : Nil#
Appends a String representation of this object which includes its class name, its object address and the values of all instance variables.
class Person
def initialize(@name : String, @age : Int32)
end
end
Person.new("John", 32).inspect # => #<Person:0x10fd31f20 @name="John", @age=32>
#loaded? : Bool#
Returns true if the target entity has been loaded, or the proxy was created by .wrap.