Class: Dux::Comparable

Inherits:
Module
  • Object
show all
Defined in:
lib/dux/comparable.rb

Overview

Simplify the creation of comparable objects by specifying a list of attributes (with optional ordering) that determines how they should be compared, without having to define a spaceship (<=>) operator in the given class.

Defined Under Namespace

Classes: Attribute

Constant Summary collapse

ORDERS =

Valid orders for sorting

Dux.enum(:asc, :desc, aliases: { ascending: :asc, descending: :desc })
ATTRIBUTE_TUPLE =

Checks if an attribute argument is a valid tuple

Dux.yard('(Symbol, Symbol)')

Instance Method Summary collapse

Constructor Details

#initialize(*attributes, sort_order: :asc, type_guard: true, **options) ⇒ Comparable

Returns a new instance of Comparable.

Parameters:

  • attributes (<Symbol, (Symbol, Symbol)>)
  • type_guard (Boolean, String) (defaults to: true)
  • sort_order (:asc, :desc, :ascending, :descending) (defaults to: :asc)


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/dux/comparable.rb', line 20

def initialize(*attributes, sort_order: :asc, type_guard: true, **options)
  @default_sort_order = validate_order sort_order

  @type_guard = validate_type_guard type_guard

  @attributes = parse_attributes(attributes)

  if @attributes.one?
    @default_sort_order = @attributes.first.order
  end

  include ::Comparable

  class_eval spaceship_method, __FILE__, __LINE__ + 1
end

Instance Method Details

#build_spaceship_methodString (private)

Generates the spaceship method body.

Returns:

  • (String)


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/dux/comparable.rb', line 98

def build_spaceship_method
  ''.tap do |body|

    body << <<-RUBY
      def <=>(other)
    RUBY

    if type_guard?
      body << <<-RUBY
          unless other.kind_of?(#{type_guard_value})
            raise TypeError, "\#{other.inspect} must be kind of \#{#{type_guard_value}}"
          end
      RUBY
    end

    body << <<-RUBY
        #{join_attributes}
    RUBY

    body << <<-RUBY
      end
    RUBY
  end
end

#inspectString

Display the attributes used to compare for clarity in class ancestor listings.

Returns:

  • (String)


40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/dux/comparable.rb', line 40

def inspect
  attribute_list = @attributes.map do |attribute|
    attribute.to_inspect(many: many?, default: @default_sort_order)
  end.join(', ')

  if many?
    attribute_list = "[#{attribute_list}]"

    attribute_list << ", default_order: #{@default_sort_order.to_s.upcase}"
  end

  "Dux::Comparable(#{attribute_list})"
end

#join_attributesString (private)

Join the attributes to be checked in #build_spaceship_method

Returns:

  • (String)

See Also:

  • Dux::Comparable.[Dux[Dux::Enum[Dux::Enum::Attribute[Dux::Enum::Attribute#to_comparison]


127
128
129
130
131
# File 'lib/dux/comparable.rb', line 127

def join_attributes
  @attributes.map do |attribute|
    attribute.to_comparison(wrap: @attributes.length > 1)
  end.join('.nonzero? || ')
end

#many?Boolean

Determine if we are operating on many attributes

Boolean complement of #single?.

Returns:

  • (Boolean)


57
58
59
# File 'lib/dux/comparable.rb', line 57

def many?
  @attributes.length > 1
end

#parse_attributes(attributes) ⇒ <Dux::Enum::Attribute> (private)

Parameters:

  • attributes (<Symbol, (Symbol, Symbol)>)

Returns:

  • (<Dux::Enum::Attribute>)

Raises:

  • (ArgumentError)


147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/dux/comparable.rb', line 147

def parse_attributes(attributes)
  raise ArgumentError, "Must provide at least one attribute" if attributes.empty?

  attributes.map do |attribute|
    case attribute
    when Symbol, String
      Attribute.new(attribute, @default_sort_order)
    when ATTRIBUTE_TUPLE
      Attribute.new(attribute[0], validate_order(attribute[1]))
    else
      raise ArgumentError, "Don't know what to do with #{attribute.inspect}"
    end
  end.freeze
end

#same_type_guard?Boolean

Determine if we have a type guard that requires another of the same class.

Returns:

  • (Boolean)


70
71
72
# File 'lib/dux/comparable.rb', line 70

def same_type_guard?
  @type_guard == true
end

#single?Boolean

Determine if we are operating on a single attribute

Boolean complement of #many?.

Returns:

  • (Boolean)


64
65
66
# File 'lib/dux/comparable.rb', line 64

def single?
  @attributes.one?
end

#spaceship_methodString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (String)


89
90
91
# File 'lib/dux/comparable.rb', line 89

def spaceship_method
  @spaceship_method ||= build_spaceship_method
end

#specific_type_guard?Boolean

Determine if

Returns:

  • (Boolean)


75
76
77
# File 'lib/dux/comparable.rb', line 75

def specific_type_guard?
  @type_guard.kind_of?(String) && Dux.presentish?(@type_guard)
end

#type_guard?Boolean

Determine if we have any kind of type guard.

Returns:

  • (Boolean)

See Also:

  • Dux::Comparable.[[#same_type_guard?]
  • Dux::Comparable.[[#specific_type_guard?]


83
84
85
# File 'lib/dux/comparable.rb', line 83

def type_guard?
  same_type_guard? || specific_type_guard?
end

#type_guard_valueString (private)

Provides the value for type guards used by #build_spaceship_method

Returns:

  • (String)


135
136
137
138
139
140
141
142
143
# File 'lib/dux/comparable.rb', line 135

def type_guard_value
  raise 'Cannot get value for non-type guard' unless type_guard?

  if same_type_guard?
    'self.class'
  elsif specific_type_guard?
    @type_guard
  end
end

#validate_order(sort_order) ⇒ Symbol (private)

Parameters:

  • sort_order (Symbol, String)

Returns:

  • (Symbol)

Raises:

  • (ArgumentError)

    when given an improper sort order



165
166
167
168
169
# File 'lib/dux/comparable.rb', line 165

def validate_order(sort_order)
  ORDERS[sort_order]
rescue Dux::Enum::NotFound => e
  raise ArgumentError, "invalid sort order: #{sort_order.inspect}"
end

#validate_type_guard(type_guard) ⇒ Boolean, ... (private)

Parameters:

  • type_guard (Boolean, String, Symbol)

Returns:

  • (Boolean, String, Symbol)

Raises:

  • (TypeError)

    when given an improper type guard



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/dux/comparable.rb', line 174

def validate_type_guard(type_guard)
  case type_guard
  when true, false, nil then type_guard
  when Class, Module
    type_guard.name
  when String, Symbol, Dux::IndifferentString
    type_guard.to_s
  else
    raise TypeError, "Don't know what to do with type guard: #{type_guard.inspect}"
  end
end