module Cistern::Attributes::InstanceMethods

Public Instance Methods

attributes() click to toggle source
# File lib/cistern/attributes.rb, line 172
def attributes
  @attributes ||= {}
end
attributes=(new_attributes = {})
Alias for: merge_attributes
changed() click to toggle source
# File lib/cistern/attributes.rb, line 265
def changed
  @changes ||= {}
end
clone_attributes() click to toggle source
# File lib/cistern/attributes.rb, line 131
def clone_attributes
  Marshal.load Marshal.dump(attributes)
end
dirty?() click to toggle source
# File lib/cistern/attributes.rb, line 257
def dirty?
  changed.any?
end
dirty_attributes() click to toggle source
# File lib/cistern/attributes.rb, line 261
def dirty_attributes
  changed.inject({}) { |r, (k, (_, v))| r.merge(k => v) }
end
dirty_request_attributes() click to toggle source
# File lib/cistern/attributes.rb, line 277
def dirty_request_attributes
  request_attributes(dirty_attributes)
end
dup() click to toggle source
Calls superclass method
# File lib/cistern/attributes.rb, line 176
def dup
  super.tap { |m| m.set_attributes attributes.dup }
end
identity() click to toggle source
# File lib/cistern/attributes.rb, line 180
def identity
  key = self.class.identity

  public_send(key) if key
end
identity=(new_identity) click to toggle source
# File lib/cistern/attributes.rb, line 186
def identity=(new_identity)
  key = self.class.identity

  if key
    public_send("#{key}=", new_identity)
  else
    fail ArgumentError, 'Identity not specified'
  end
end
merge_attributes(new_attributes = {}) click to toggle source

Update model's attributes. New attributes take precedence over existing attributes.

This is bst called within a {Cistern::Model#save}, when {#new_attributes} represents a recently presented remote resource. {#dirty_attributes} is cleared after merging.

@param new_attributes [Hash] attributes to merge with current attributes

# File lib/cistern/attributes.rb, line 202
def merge_attributes(new_attributes = {})
  _merge_attributes(new_attributes)

  changed.clear

  self
end
Also aliased as: attributes=
new_record?() click to toggle source
# File lib/cistern/attributes.rb, line 223
def new_record?
  identity.nil?
end
read_attribute(name) click to toggle source
# File lib/cistern/attributes.rb, line 135
def read_attribute(name)
  key = name.to_sym

  options = self.class.attributes[key]
  default = options[:default]

  # record the attribute was accessed
  if defined?(Cistern::Coverage) && options[:coverage_hits]
    options[:coverage_hits] += 1
  end

  default = Marshal.load(Marshal.dump(default)) unless default.nil?

  attributes.fetch(key, default)
end
request_attributes(set = attributes) click to toggle source
# File lib/cistern/attributes.rb, line 269
def request_attributes(set = attributes)
  set.inject({}) do |a,(k,v)|
    aliases = self.class.attributes[k.to_sym][:aliases]
    aliases << k if aliases.empty?
    aliases.each_with_object(a) { |n,r| r[n.to_s] = v }
  end
end
requires(*args) click to toggle source

Require specification of certain attributes

@raise [ArgumentError] if any requested attribute does not have a value @return [Hash] of matching attributes

# File lib/cistern/attributes.rb, line 231
def requires(*args)
  missing, required = missing_attributes(args)

  if missing.length == 1
    fail(ArgumentError, "#{missing.keys.first} is required for this operation")
  elsif missing.any?
    fail(ArgumentError, "#{missing.keys[0...-1].join(', ')} and #{missing.keys[-1]} are required for this operation")
  end

  required
end
requires_one(*args) click to toggle source

Require specification of one or more attributes.

@raise [ArgumentError] if no requested attributes have values @return [Hash] of matching attributes

# File lib/cistern/attributes.rb, line 247
def requires_one(*args)
  missing, required = missing_attributes(args)

  if missing.length == args.length
    fail(ArgumentError, "#{missing.keys[0...-1].join(', ')} or #{missing.keys[-1]} are required for this operation")
  end

  required
end
stage_attributes(new_attributes = {}) click to toggle source

Update model's attributes. New attributes take precedence over existing attributes.

This is best called within a {Cistern::Model#update}, when {#new_attributes} represents attributes to be presented to a remote service. {#dirty_attributes} will contain the valid portion of {#new_attributes}

@param new_attributes [Hash] attributes to merge with current attributes

# File lib/cistern/attributes.rb, line 218
def stage_attributes(new_attributes = {})
  _merge_attributes(new_attributes)
  self
end
write_attribute(name, value) click to toggle source
# File lib/cistern/attributes.rb, line 151
def write_attribute(name, value)
  options = self.class.attributes[name] || {}

  transform = options[:transform]

  parser = options[:parser]

  transformed = transform.call(name, value, options)

  new_value = parser.call(transformed, options)
  attribute = name.to_s.to_sym

  previous_value = read_attribute(name)

  attributes[attribute] = new_value

  changed!(attribute, previous_value, new_value)

  new_value
end

Protected Instance Methods

set_attributes(attributes) click to toggle source
# File lib/cistern/attributes.rb, line 332
def set_attributes(attributes)
  @attributes = attributes
end

Private Instance Methods

_merge_attributes(new_attributes) click to toggle source
# File lib/cistern/attributes.rb, line 297
def _merge_attributes(new_attributes)
  protected_methods  = (Cistern::Model.instance_methods - PROTECTED_METHODS)
  ignored_attributes = self.class.ignored_attributes
  specifications     = self.class.attributes
  class_aliases      = self.class.aliases

  # this has the side effect of dup'ing the incoming hash
  new_attributes = Cistern::Hash.stringify_keys(new_attributes)

  new_attributes.each do |key, value|
    symbol_key = key.to_sym

    # find nested paths
    value.is_a?(::Hash) && specifications.each do |name, options|
      if options[:squash] && options[:squash].first == key
        send("#{name}=", key => value)
      end
    end

    next if ignored_attributes.include?(symbol_key)

    if class_aliases.key?(symbol_key)
      class_aliases[symbol_key].each { |attribute_alias| public_send("#{attribute_alias}=", value) }
    end

    assignment_method = "#{key}="

    if !protected_methods.include?(symbol_key) && self.respond_to?(assignment_method, true)
      public_send(assignment_method, value)
    end
  end
end
changed!(attribute, from, to) click to toggle source
# File lib/cistern/attributes.rb, line 289
def changed!(attribute, from, to)
  changed[attribute] = if existing = changed[attribute]
                         [existing.first, to]
                       else
                         [from, to]
                       end
end
missing_attributes(keys) click to toggle source
# File lib/cistern/attributes.rb, line 283
def missing_attributes(keys)
  keys.map(&:to_sym).reduce({}) { |a,e| a.merge(e => public_send("#{e}")) }
    .partition { |_,v| v.nil? }
    .map { |s| Hash[s] }
end