loading
Generated 2025-09-16T23:58:56+00:00

All Files ( 100.0% covered at 50.08 hits/line )

2 files in total.
60 relevant lines, 60 lines covered and 0 lines missed. ( 100.0% )
6 total branches, 6 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/wholeable.rb 100.00 % 8 3 3 0 1.00 100.00 % 0 0 0
lib/wholeable/builder.rb 100.00 % 108 57 57 0 52.67 100.00 % 6 6 0

lib/wholeable.rb

100.0% lines covered

100.0% branches covered

3 relevant lines. 3 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "wholeable/builder"
  3. # Main namespace.
  4. 1 module Wholeable
  5. 1 def self.[](*, **) = Builder.new(*, **)
  6. end

lib/wholeable/builder.rb

100.0% lines covered

100.0% branches covered

57 relevant lines. 57 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Wholeable
  3. # Provides core equality behavior.
  4. 1 class Builder < Module
  5. 1 def self.add_aliases descendant
  6. 84 descendant.alias_method :deconstruct, :to_a
  7. 84 descendant.alias_method :deconstruct_keys, :to_h
  8. 84 descendant.alias_method :to_s, :inspect
  9. end
  10. 1 def initialize *keys, kind: :immutable
  11. 84 super()
  12. 84 @keys = keys.uniq
  13. 84 @kind = kind
  14. 84 @members = []
  15. 84 setup
  16. end
  17. 1 def included descendant
  18. 84 super
  19. 84 coalesce_members descendant
  20. 84 descendant.class_eval <<-METHODS, __FILE__, __LINE__ + 1
  21. 1 def self.[](...) = new(...)
  22. 2 def self.new(...) = #{mutable?} ? super.dup : super.freeze
  23. 1 def self.members = #{members}
  24. 1 then: 7 else: 77 #{mutable? ? :attr_accessor : :attr_reader} #{keys.map(&:inspect).join ", "}
  25. METHODS
  26. 84 self.class.add_aliases descendant
  27. end
  28. 1 private
  29. 1 attr_reader :keys, :kind, :members
  30. 1 def setup
  31. 840 private_methods.grep(/\A(define)_/).sort.each { |method| __send__ method }
  32. 84 freeze
  33. end
  34. 1 def coalesce_members descendant
  35. 84 then: 15 else: 69 members.replace(descendant.respond_to?(:members) ? (descendant.members + keys).uniq : keys)
  36. end
  37. 1 def mutable? = kind == :mutable
  38. 1 def define_diff
  39. 84 define_method :diff do |other|
  40. 6 then: 4 if other.is_a? self.class
  41. 12 to_h.merge!(other.to_h) { |_, one, two| [one, two].uniq }
  42. 8 .select { |_, diff| diff.size == 2 }
  43. else: 2 else
  44. 6 to_h.each.with_object({}) { |(key, value), diff| diff[key] = [value, nil] }
  45. end
  46. end
  47. end
  48. 1 def define_eql
  49. 90 define_method(:eql?) { |other| instance_of?(other.class) && hash == other.hash }
  50. end
  51. 1 def define_equality
  52. 90 define_method(:==) { |other| other.is_a?(self.class) && hash == other.hash }
  53. end
  54. 1 def define_hash
  55. 84 define_method :hash do
  56. 78 members.map { |key| public_send key }
  57. .prepend(self.class)
  58. .hash
  59. end
  60. end
  61. 1 def define_inspect
  62. 84 define_method :inspect do
  63. 5 klass = self.class
  64. 5 name = klass.name || klass.inspect
  65. 15 members.map { |key| "@#{key}=#{public_send(key).inspect}" }
  66. .join(", ")
  67. 5 .then { |pairs| "#<#{name} #{pairs}>" }
  68. end
  69. end
  70. 61 def define_members(local_members = members) = define_method(:members) { local_members }
  71. 1 def define_to_a
  72. 84 define_method :to_a do
  73. 12 members.reduce([]) { |collection, key| collection.append public_send(key) }
  74. end
  75. end
  76. 1 def define_to_h
  77. 84 define_method :to_h do
  78. 60 members.each.with_object({}) { |key, attributes| attributes[key] = public_send key }
  79. end
  80. end
  81. 1 def define_with
  82. 90 define_method(:with) { |**attributes| self.class.new(**to_h.merge!(attributes)) }
  83. end
  84. end
  85. end