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

@example

class Person
  include Shamu::Attributes

  attribute :name
end

Public Class Methods

new( *attributes ) click to toggle source
# File lib/shamu/attributes.rb, line 35
def initialize( *attributes )
  assign_attributes( attributes.last )
end

Public Instance Methods

[]( name ) click to toggle source

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
as_json( options = {} ) click to toggle source

@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
attribute?( name ) click to toggle source

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
Also aliased as: key?
key?( name )
Alias for: attribute?
pretty_print( pp ) click to toggle source
# 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
set?( attribute ) click to toggle source

@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
slice( *names ) click to toggle source

@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
to_attributes( only: nil, except: nil ) click to toggle source

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
to_json( options = {} ) click to toggle source

@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

assign_attributes( attributes ) click to toggle source

@!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
association( name, *args, **options, &block ) click to toggle source

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
associations() click to toggle source

@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
attribute( name, *args, **options, &block ) click to toggle source

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
attribute_option_keys() click to toggle source

@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
attributes() click to toggle source

@return [Hash] of attributes and their options defined on the class.

# File lib/shamu/attributes.rb, line 173
def attributes
  @attributes ||= {}
end
build_value( build, value ) click to toggle source
# 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
create_attribute( name, *args, **options ) click to toggle source
# 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
define_attribute_assignment( name, ** ) click to toggle source
# 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
define_attribute_reader( name, as: nil, ** ) click to toggle source
# 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
define_delegate_fetcher( name, on, builder ) click to toggle source
# 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
define_virtual_fetcher( name, default, &block ) click to toggle source
# 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
inherited( subclass ) click to toggle source
Calls superclass method
# 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
match_attribute?( pattern, name ) click to toggle source
# File lib/shamu/attributes.rb, line 107
def match_attribute?( pattern, name )
  Array( pattern ).any? do |matcher|
    matcher === name
  end
end
resolve_attributes( attributes ) click to toggle source
# 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
serialize_attribute?( _name, options ) click to toggle source

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