Module: MobilityActiveStorage::BackendMethods

Included in:
Backend, ManyBackend
Defined in:
lib/mobility_active_storage/backend_methods.rb,
sig/mobility_active_storage.rbs

Overview

Behaviour shared by the has_one and has_many attachment backends.

A translated attachment is stored as N ordinary Active Storage attachments whose name column carries the locale, e.g. document_en and document_fr. Each one is declared with a real has_one_attached / has_many_attached in the backend's setup block, so Rails' own associations, upload callbacks, scopes and purge-on-destroy behaviour apply unchanged.

Backends must include Mobility::Backend before this module, so that the overrides here (notably each_locale and present?) take precedence over Mobility::Backend's generic implementations.

Defined Under Namespace

Modules: ClassMethods Classes: FallbackChain

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Raises:



18
19
20
21
22
# File 'lib/mobility_active_storage/backend_methods.rb', line 18

def self.included(base)
  raise Error, "#{base} must include Mobility::Backend before #{self}" unless base < Mobility::Backend

  base.extend(ClassMethods)
end

Instance Method Details

#attached(locale) ⇒ Object

The Active Storage proxy for locale, without any fallback handling.

Parameters:

  • locale (Symbol)

Returns:

  • (Object)


167
168
169
170
171
# File 'lib/mobility_active_storage/backend_methods.rb', line 167

def attached(locale)
  @attached ||= {}
  @attached[Mobility.normalize_locale(locale)] ||=
    attached_class.new(attachment_name(locale), model)
end

#each_locale {|arg0| ... } ⇒ void

This method returns an undefined value.

Yields each configured locale that actually has an attachment.

One query for the whole locale set, not one per locale: every locale is a row in active_storage_attachments under a suffixed name, so which locales are present is a single pluck on that table. Probing each proxy instead costs a query per configured locale, and a model declared for a large locale set pays that on every read -- 73 statements to answer a question one statement answers.

Pending changes win over the stored rows, as Active Storage's own attached? does: an attach counts before it is saved, a purge stops counting straight away.

Yields:

Yield Parameters:

  • arg0 (Symbol)

Yield Returns:

  • (void)


155
156
157
158
159
160
161
162
163
164
# File 'lib/mobility_active_storage/backend_methods.rb', line 155

def each_locale
  persisted = persisted_attachment_names

  options[:locales].each do |locale|
    name = attachment_name(locale)
    # attached? reads the staged change without touching the database.
    present = model.attachment_changes.key?(name) ? attached(locale).attached? : persisted.include?(name)
    yield locale if present
  end
end

#present?(locale, **options) ⇒ Boolean

Whether the attribute has an attachment in locale (honouring fallbacks).

Parameters:

  • locale (Symbol)
  • (Object)

Returns:

  • (Boolean)


141
142
143
# File 'lib/mobility_active_storage/backend_methods.rb', line 141

def present?(locale, **options)
  read(locale, **options).attached?
end

#read(locale, fallback: true, **kwargs) ⇒ ActiveStorage::Attached::One, ActiveStorage::Attached::Many

Returns the Active Storage proxy for locale.

Always returns a proxy, never nil, so that record.document.attach(...) works on a record with nothing attached yet. Falls back to another locale only when the attribute was configured with fallbacks and the current locale has nothing attached.

Parameters:

  • locale (Symbol)
  • fallback: (bool, Symbol, Array[Symbol]) (defaults to: true)
  • (Object)

Returns:

  • (ActiveStorage::Attached::One, ActiveStorage::Attached::Many)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/mobility_active_storage/backend_methods.rb', line 111

def read(locale, fallback: true, **kwargs)
  proxy = attached(locale)
  return proxy if proxy.attached?

  # Mobility's convention: an explicitly requested locale never falls back.
  return proxy if fallback == false || kwargs[:locale]

  fallback_locale = fallback_chain(locale, fallback).find do |candidate|
    configured?(candidate) && attached(candidate).attached?
  end
  return proxy unless fallback_locale

  # Reads resolve through the fallback locale; writes stay in the requested locale.
  fallback_attached_class.new(attachment_name(locale), model, attachment_name(fallback_locale))
end

#write(locale, value) ⇒ Object

Stages the attachment change for locale.

Deliberately mutates attachment_changes rather than calling attach. ActiveStorage::Attached::One#attach is implemented as record.public_send("document_en=", attachable), and Mobility's locale accessors take precedence over Active Storage's generated writer -- so calling attach here would re-enter this same method. Writing the change directly makes both entry paths converge.

Parameters:

  • locale (Symbol)
  • value (Object)
  • (Object)

Returns:

  • (Object)


134
135
136
137
138
# File 'lib/mobility_active_storage/backend_methods.rb', line 134

def write(locale, value, **)
  name = attachment_name(locale)
  model.attachment_changes[name] = build_change(name, value)
  value
end