Class: Mobility::Attributes

Inherits:
Module
  • Object
show all
Defined in:
lib/mobility/attributes.rb

Overview

Defines accessor methods to include on model class. Inspired by Traco's Traco::Attributes class.

Normally this class will be created through class methods defined using Translates accessor methods, and need not be created directly. However, the class is central to how Mobility hooks into models to add accessors and other methods, and should be useful as a reference when understanding and designing backends.

Including Attributes in a Class

Since Attributes is a subclass of Module, including an instance of it is like including a module. Creating an instance like this:

Attributes.new("title", backend: :my_backend, locale_accessors: [:en, :ja], cache: true, fallbacks: true)

will generate an anonymous module that behaves approximately like this:

Module.new do
  def mobility_backends
    # Returns a memoized hash with attribute name keys and backend instance
    # values.  When a key is fetched from the hash, the hash calls
    # +self.class.mobility_backend_class(name)+ (where +name+ is the
    # attribute name) to get the backend class, then instantiate it (passing
    # the model instance and attribute name to its initializer) and return it.
    #
    # The backend class returned from the class method
    # +mobility_backend_class+ returns a subclass of
    # +Mobility::Backends::MyBackend+ and includes into it:
    #
    # - Mobility::Plugins::Cache (from the +cache: true+ option)
    # - instance of Mobility::Plugins::Fallbacks (from the +fallbacks: true+ option)
    # - Mobility::Plugins::Presence (by default, disabled by +presence: false+)
  end

  def title(locale: Mobility.locale)
    mobility_backends[:title].read(locale)
  end

  def title?(locale: Mobility.locale)
    mobility_backends[:title].read(locale).present?
  end

  def title=(value, locale: Mobility.locale)
    mobility_backends[:title].write(locale, value)
  end

  # Start Locale Accessors
  #
  def title_en
    title(locale: :en)
  end

  def title_en?
    title?(locale: :en)
  end

  def title_en=(value)
    public_send(:title=, value, locale: :en)
  end

  def title_ja
    title(locale: :ja)
  end

  def title_ja?
    title?(locale: :ja)
  end

  def title_ja=(value)
    public_send(:title=, value, locale: :ja)
  end
  # End Locale Accessors
end

Including this module into a model class will thus add the backend method, the reader, writer and presence methods, and the locale accessor so the model class. (These methods are in fact added to the model in an included hook.)

Note that some simplifications have been made above for readability. (In reality, all getters and setters accept an options hash which is passed along to the backend instance.)

Setting up the Model Class

Accessor methods alone are of limited use without a hook to actually modify the model class. This hook is provided by the Backend::Setup#setup_model method, which is added to every backend class when it includes the Backend module.

Assuming the backend has defined a setup block by calling setup, this block will be called when Attributes is #included in the model class, passed attributes and options defined when the backend was defined on the model class. This allows a backend to do things like (for example) define associations on a model class required by the backend, as happens in the Backends::KeyValue and Backends::Table backends.

Since setup blocks are evaluated on the model class, it is possible that backends can conflict (for example, overwriting previously defined methods). Care should be taken to avoid defining methods on the model class, or where necessary, ensure that names are defined in such a way as to avoid conflicts with other backends.

Defined Under Namespace

Modules: ClassMethods, InstanceMethods

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*attribute_names, method: :accessor, backend: Mobility.default_backend, **backend_options) ⇒ Attributes

Returns a new instance of Attributes

Options Hash (**backend_options):

  • model_class (Class)

    Class of model

Raises:

  • (ArgumentError)

    if method is not reader, writer or accessor


141
142
143
144
145
146
147
148
# File 'lib/mobility/attributes.rb', line 141

def initialize(*attribute_names, method: :accessor, backend: Mobility.default_backend, **backend_options)
  raise ArgumentError, "method must be one of: reader, writer, accessor" unless %i[reader writer accessor].include?(method)
  @method = method
  @options = Mobility.default_options.to_h.merge(backend_options)
  @names = attribute_names.map(&:to_s).freeze
  raise BackendRequired, "Backend option required if Mobility.config.default_backend is not set." if backend.nil?
  @backend_name = backend
end

Instance Attribute Details

#backend_classClass (readonly)

Backend class


126
127
128
# File 'lib/mobility/attributes.rb', line 126

def backend_class
  @backend_class
end

#backend_nameSymbol, Class (readonly)

Name of backend


130
131
132
# File 'lib/mobility/attributes.rb', line 130

def backend_name
  @backend_name
end

#methodSymbol (readonly)

Method (accessor, reader or writer)


114
115
116
# File 'lib/mobility/attributes.rb', line 114

def method
  @method
end

#model_classClass (readonly)

Model class


134
135
136
# File 'lib/mobility/attributes.rb', line 134

def model_class
  @model_class
end

#namesArray<String> (readonly)

Attribute names for which accessors will be defined


118
119
120
# File 'lib/mobility/attributes.rb', line 118

def names
  @names
end

#optionsHash (readonly)

Backend options


122
123
124
# File 'lib/mobility/attributes.rb', line 122

def options
  @options
end

Instance Method Details

#each {|Attribute| ... } ⇒ Object

Yield each attribute name to block

Yield Parameters:

  • Attribute (String)

177
178
179
# File 'lib/mobility/attributes.rb', line 177

def each &block
  names.each(&block)
end

#included(klass) ⇒ Object

Setup backend class, include modules into model class, include/extend shared modules and setup model with backend setup block (see Backend::Setup#setup_model).


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/mobility/attributes.rb', line 154

def included(klass)
  @model_class = @options[:model_class] = klass
  @backend_class = get_backend_class(backend_name).for(model_class).with_options(options)

  Mobility.plugins.each do |name|
    plugin = get_plugin_class(name)
    plugin.apply(self, options[name])
  end

  each do |name|
    define_backend(name)
    define_reader(name) if %i[accessor reader].include?(method)
    define_writer(name) if %i[accessor writer].include?(method)
  end

  klass.include InstanceMethods
  klass.extend ClassMethods

  backend_class.setup_model(model_class, names)
end

#inspectString

Show useful information about this module.


183
184
185
# File 'lib/mobility/attributes.rb', line 183

def inspect
  "#<Attributes (#{backend_name}) @names=#{names.join(", ")}>"
end