module Shamu::Attributes
Provide attributes that project data from another source (such as an external API, ActiveRecord model, cached data, etc.) providing simple transformations.
To add additional attribute functionality see
-
{Attributes::Assignment}
-
{Attributes::FluidAssignment}
-
{Attributes::Validation}
-
{Attributes::Equality}
-
{Attributes::HtmlSanitation}
@example
class Person include Shamu::Attributes attribute :name end
Public Class Methods
# File lib/shamu/attributes.rb, line 35 def initialize( *attributes ) assign_attributes( attributes.last ) end
Public Instance Methods
Access an attribute using a Hash like index. @param [Symbol] name of the attribute. @return [Object]
# File lib/shamu/attributes.rb, line 69 def []( name ) send name if attribute?( name ) end
@return [Hash] the assigned attributes as a hash for JSON serialization.
# File lib/shamu/attributes.rb, line 80 def as_json( options = {} ) # rubocop:disable Styles/OptionHash to_attributes( options.slice( :only, :except ) ).as_json end
Indicates if the object has an attribute with the given name. Aliased to {#key?} to make the object look like a Hash.
# File lib/shamu/attributes.rb, line 61 def attribute?( name ) self.class.attributes.key?( name.to_sym ) end
# File lib/shamu/attributes.rb, line 89 def pretty_print( pp ) attributes = to_attributes pp.object_address_group( self ) do pp.seplist( attributes.keys, -> { pp.text "," } ) do |name| pp.breakable " " pp.group( 1 ) do pp.text name.to_s pp.text ":" pp.breakable " " pp.pp attributes[ name ] end end end end
@param [Symbol] attribute name. @return [Boolean] true if the attribute has been set.
# File lib/shamu/attributes.rb, line 75 def set?( attribute ) instance_variable_defined? :"@#{ attribute }" end
@return [Hash] a hash with the keys for each of the given names.
# File lib/shamu/attributes.rb, line 55 def slice( *names ) to_attributes only: names end
Project the current state of the object to a hash of attributes that can be used to restore the attribute object at a later time.
@param [Array, Regex] only include matching attributes @param [Array, Regex] except matching attributes @return [Hash] of attributes
# File lib/shamu/attributes.rb, line 45 def to_attributes( only: nil, except: nil ) self.class.attributes.each_with_object({}) do |(name, options), attrs| next if ( only && !match_attribute?( only, name ) ) || ( except && match_attribute?( except, name ) ) next unless serialize_attribute?( name, options ) attrs[name] = send( name ) end end
@return [String] JSON encoded version of the assigned attributes.
# File lib/shamu/attributes.rb, line 85 def to_json( options = {} ) # rubocop:disable Styles/OptionHash as_json( options ).to_json end
Private Instance Methods
@!visibility public
Assign a hash of values to the matching instance variables.
@param [Hash] attributes to assign.
@return [self]
# File lib/shamu/attributes.rb, line 126 def assign_attributes( attributes ) attributes = resolve_attributes( attributes ) self.class.attributes.each do |key, options| as = options[ :as ] # Alias support next unless attributes.key?( key ) || ( as && attributes.key?( as ) ) value = attributes[ key ] value ||= attributes[ as ] if as if build = options[:build] value = build_value( build, value ) end send :"assign_#{ key }", value end end
Define an {.attribute} that defines an association to another resource that also has it's own attributes.
@param (see .attribute) @yieldreturn (see .attribute) @return [self]
# File lib/shamu/attributes.rb, line 230 def association( name, *args, **options, &block ) options[:association] = true attribute( name, *args, **options, &block ) end
@return [Hash] of all association {.attributes} defined on the class.
# File lib/shamu/attributes.rb, line 178 def associations attributes.select { |_, v| v[:association] } end
Define a new attribute for the class.
@overload attribute(name, on:, default:, build:, &block ) @overload attribute(name, build, on:, default:, &block)
@param [Symbol] name of the attribute. @param [Symbol] as an alias of the attribute. @param [Symbol] on another method on the class to delegate the attribute
to.
@param [Object,#call] default value if not set. @param [Class,#call] build method used to build a nested object on
assignment of a hash with nested keys.
@param [Boolean] serialize true if the attribute should be included in
{#to_attributes}. Default true.
@yieldreturn the value of the attribute. The result is memoized so the
block is only invoked once.
@return [self]
# File lib/shamu/attributes.rb, line 205 def attribute( name, *args, **options, &block ) name = name.to_sym options = create_attribute( name, *args, **options ) define_attribute_reader( name, **options ) define_attribute_assignment( name, **options ) if options.key?( :on ) define_delegate_fetcher( name, options[:on], options[:build] ) else define_virtual_fetcher( name, options[:default], &block ) end private :"fetch_#{ name }" private :"assign_#{ name }" self end
@return [Array<Symbol>] keys used by the {.attribute} method options
argument. Used by {Attributes::Validation} to filter option keys.
# File lib/shamu/attributes.rb, line 240 def attribute_option_keys [ :on, :build, :default, :serialize, :as ] end
@return [Hash] of attributes and their options defined on the class.
# File lib/shamu/attributes.rb, line 173 def attributes @attributes ||= {} end
# File lib/shamu/attributes.rb, line 143 def build_value( build, value ) if build.is_a?( Class ) build.new( value ) elsif build.is_a?( Symbol ) value.send( build ) else build.call( value ) end end
# File lib/shamu/attributes.rb, line 244 def create_attribute( name, *args, **options ) options = options.dup options[:build] = args[0] unless args.blank? options[:serialize] = options.fetch( :serialize, true ) attributes[name] = options end
# File lib/shamu/attributes.rb, line 303 def define_attribute_assignment( name, ** ) class_eval <<-RUBY, __FILE__, __LINE__ + 1 def assign_#{ name }( value ) # assign_attribute( value ) @#{ name } = value # @attribute = value end # end RUBY end
# File lib/shamu/attributes.rb, line 251 def define_attribute_reader( name, as: nil, ** ) class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{ name } # def attribute return @#{ name } if defined? @#{ name } # return @attribute if defined? @attribute @#{ name } = fetch_#{ name } # @attribute = fetch_attribute end # end def #{ name }_set? # def attribute_set? defined? @#{ name } # defined? @attribute end # end RUBY alias_method as, name if as end
# File lib/shamu/attributes.rb, line 284 def define_delegate_fetcher( name, on, builder ) if builder define_method :"build_#{ name }" do |value| build_value( builder, value ) end class_eval <<-RUBY, __FILE__, __LINE__ + 1 def fetch_#{ name } # fetch_attribute #{ on } && build_#{ name }( #{ on }.#{ name } ) # target && build_attribute( target.attribute ) end # end RUBY else class_eval <<-RUBY, __FILE__, __LINE__ + 1 def fetch_#{ name } # fetch_attribute #{ on } && #{ on }.#{ name } # target && target.attribute end # end RUBY end end
# File lib/shamu/attributes.rb, line 266 def define_virtual_fetcher( name, default, &block ) method_name = :"fetch_#{ name }" if block_given? define_method method_name, &block elsif default.respond_to?( :call ) && !default.is_a?( Symbol ) define_method method_name, &default elsif default define_method method_name do default end else class_eval <<-RUBY, __FILE__, __LINE__ + 1 def fetch_#{ name }; @#{ name } end # def fetch_attribute; @attribute end RUBY end end
# File lib/shamu/attributes.rb, line 182 def inherited( subclass ) # Clone the base class's attributes into the subclass subclass.instance_variable_set :@attributes, attributes.dup super end
# File lib/shamu/attributes.rb, line 107 def match_attribute?( pattern, name ) Array( pattern ).any? do |matcher| matcher === name end end
# File lib/shamu/attributes.rb, line 153 def resolve_attributes( attributes ) if attributes.respond_to?( :to_attributes ) attributes.to_attributes # Allow protected attributes to be used without explicitly being set. # All 'Attributes' classes are themselves the explicit set of permitted # attributes so there is no danger of 'over assignment'. elsif attributes.respond_to?( :to_unsafe_h ) attributes.to_unsafe_h elsif attributes.respond_to?( :to_hash ) attributes.to_hash.symbolize_keys elsif attributes.respond_to?( :to_h ) attributes.to_h.symbolize_keys else attributes end end
Hook for derived objects to explicitly filter attributes included in {#to_attributes}
# File lib/shamu/attributes.rb, line 115 def serialize_attribute?( _name, options ) options[:serialize] end