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.define_getter_for(property_name)
def self.define_getter_for(property_name) nclude?(property_name) rty_name) { |&block| self.[](property_name, &block) } _name
def self.define_setter_for(property_name)
def self.define_setter_for(property_name) ty_name}=" methods.include?(setter) r) { |value| self.[]=(property_name, value) }
def self.inherited(klass)
def self.inherited(klass) super (@subclasses ||= Set.new) << klass klass.instance_variable_set('@properties', properties.dup) klass.instance_variable_set('@getters', getters.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
required if the value is truthy.
property it represents must not be nil. The property is only
context of the Dash instance. If a Symbol is provided, the
existing Dash. If a Proc is provided, it will be run in the
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 define_getter_for(property_name) define_setter_for(property_name) @subclasses.each { |klass| klass.property(property_name, options) } if defined? @subclasses condition = options.delete(:required) if condition message = options.delete(:message) || "is required for #{name}." required_properties[property_name] = { condition: condition, message: message } elsif options.key?(:message) raise ArgumentError, 'The :message option should be used with :required option.' 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 value.nil? && required?(property) end
def assert_property_set!(property)
def assert_property_set!(property) fail_property_required_error!(property) if send(property).nil? && required?(property) 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) raise NoMethodError, "The property '#{property}' is not defined for #{self.class.name}." end
def fail_property_required_error!(property)
def fail_property_required_error!(property) raise ArgumentError, "The property '#{property}' #{self.class.required_properties[property][:message]}" end
def initialize(attributes = {}, &block)
You may initialize a Dash with an attributes hash
def initialize(attributes = {}, &block) super(&block) initialize_attributes(attributes) assert_required_attributes_set! end
def initialize_attributes(attributes)
def initialize_attributes(attributes) return unless attributes cleaned_attributes = attributes.reject { |_attr, value| value.nil? } update_attributes!(cleaned_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 required?(property)
def required?(property) return false unless self.class.required?(property) condition = self.class.required_properties[property][:condition] case condition when Proc then !!instance_exec(&condition) when Symbol then !!send(condition) else !!condition end end
def to_h
def to_h defaults = ::Hash[self.class.properties.map { |prop| [prop, self.class.defaults[prop]] }] defaults.merge(self) end
def update_attributes(attributes)
def update_attributes(attributes) return unless attributes attributes.each_pair do |att, value| self[att] = value end end
def update_attributes!(attributes)
def update_attributes!(attributes) update_attributes(attributes) self.class.defaults.each_pair do |prop, value| next unless fetch(prop, nil).nil? self[prop] = begin val = value.dup if val.is_a?(Proc) val.arity == 1 ? val.call(self) : val.call else val end rescue TypeError value end end assert_required_attributes_set! end