module Cistern::Attributes::InstanceMethods
Public Instance Methods
# File lib/cistern/attributes.rb, line 172 def attributes @attributes ||= {} end
# File lib/cistern/attributes.rb, line 265 def changed @changes ||= {} end
# File lib/cistern/attributes.rb, line 131 def clone_attributes Marshal.load Marshal.dump(attributes) end
# File lib/cistern/attributes.rb, line 257 def dirty? changed.any? end
# File lib/cistern/attributes.rb, line 261 def dirty_attributes changed.inject({}) { |r, (k, (_, v))| r.merge(k => v) } end
# File lib/cistern/attributes.rb, line 277 def dirty_request_attributes request_attributes(dirty_attributes) end
# File lib/cistern/attributes.rb, line 176 def dup super.tap { |m| m.set_attributes attributes.dup } end
# File lib/cistern/attributes.rb, line 180 def identity key = self.class.identity public_send(key) if key end
# 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
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
# File lib/cistern/attributes.rb, line 223 def new_record? identity.nil? end
# 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
# 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
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
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
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
# 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
# File lib/cistern/attributes.rb, line 332 def set_attributes(attributes) @attributes = attributes end
Private Instance Methods
# 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
# 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
# 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