class Hashie::Dash
API for defining properties as well as per-property defaults.
It is preferrable to a Struct because of the in-class
resources than something like a DataMapper resource.
lightweight data object that needs even fewer options and
Dashes are useful when you need to create a very simple
optional defaults) and only those keys may be set or read.
that has a set of defined keys that are accessible (with
A Dash is a ‘defined’ or ‘discrete’ Hash, that is, a Hash
def self.inherited(klass)
def self.inherited(klass) super (@subclasses ||= Set.new) << klass klass.instance_variable_set('@properties', properties.dup) klass.instance_variable_set('@defaults', defaults.dup) klass.instance_variable_set('@required_properties', required_properties.dup) end
def self.property(property_name, options = {})
* :message - Specify custom error message for required property
existing Dash.
property, to raise an error if a value is unset in a new or
* :required - Specify the value as required for this
Dash.
to be returned before a value is set on the property in a new
* :default - Specify a default value for this property,
as follows:
Defines a property on the Dash. Options are
def self.property(property_name, options = {}) properties << property_name if options.key?(:default) defaults[property_name] = options[:default] elsif defaults.key?(property_name) defaults.delete property_name end unless instance_methods.map(&:to_s).include?("#{property_name}=") define_method(property_name) { |&block| self.[](property_name, &block) } property_assignment = property_name.to_s.concat('=').to_sym define_method(property_assignment) { |value| self.[]=(property_name, value) } end if defined? @subclasses @subclasses.each { |klass| klass.property(property_name, options) } end if options.delete(:required) required_properties[property_name] = options.delete(:message) || "is required for #{name}." else fail ArgumentError, 'The :message option should be used with :required option.' if options.key?(:message) end end
def self.property?(name)
Check to see if the specified property has already been
def self.property?(name) properties.include? name end
def self.required?(name)
Check to see if the specified property is
def self.required?(name) required_properties.key? name end
def [](property)
Retrieve a value from the Dash (will return the
def [](property) assert_property_exists! property value = super(property) # If the value is a lambda, proc, or whatever answers to call, eval the thing! if value.is_a? Proc self[property] = value.call # Set the result of the call as a value else yield value if block_given? value end end
def []=(property, value)
Set a value on the Dash in a Hash-like way. Only works
def []=(property, value) assert_property_required! property, value assert_property_exists! property super(property, value) end
def assert_property_exists!(property)
def assert_property_exists!(property) fail_no_property_error!(property) unless self.class.property?(property) end
def assert_property_required!(property, value)
def assert_property_required!(property, value) fail_property_required_error!(property) if self.class.required?(property) && value.nil? end
def assert_property_set!(property)
def assert_property_set!(property) fail_property_required_error!(property) if send(property).nil? end
def assert_required_attributes_set!
def assert_required_attributes_set! self.class.required_properties.each_key do |required_property| assert_property_set!(required_property) end end
def fail_no_property_error!(property)
def fail_no_property_error!(property) fail NoMethodError, "The property '#{property}' is not defined for #{self.class.name}." end
def fail_property_required_error!(property)
def fail_property_required_error!(property) fail ArgumentError, "The property '#{property}' #{self.class.required_properties[property]}" end
def initialize(attributes = {}, &block)
You may initialize a Dash with an attributes hash
def initialize(attributes = {}, &block) super(&block) self.class.defaults.each_pair do |prop, value| self[prop] = begin value.dup rescue TypeError value end end initialize_attributes(attributes) assert_required_attributes_set! end
def initialize_attributes(attributes)
def initialize_attributes(attributes) attributes.each_pair do |att, value| self[att] = value end if attributes end
def merge(other_hash)
def merge(other_hash) new_dash = dup other_hash.each do |k, v| new_dash[k] = block_given? ? yield(k, self[k], v) : v end new_dash end
def merge!(other_hash)
def merge!(other_hash) other_hash.each do |k, v| self[k] = block_given? ? yield(k, self[k], v) : v end self end
def replace(other_hash)
def replace(other_hash) other_hash = self.class.defaults.merge(other_hash) (keys - other_hash.keys).each { |key| delete(key) } other_hash.each { |key, value| self[key] = value } self end
def update_attributes!(attributes)
def update_attributes!(attributes) initialize_attributes(attributes) self.class.defaults.each_pair do |prop, value| self[prop] = begin value.dup rescue TypeError value end if self[prop].nil? end assert_required_attributes_set! end