File: value_object.rb

package info (click to toggle)
ruby-virtus 2.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 660 kB
  • sloc: ruby: 4,378; makefile: 2
file content (150 lines) | stat: -rw-r--r-- 4,114 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
module Virtus

  # Include this Module for Value Object semantics
  #
  # The idea is that instances should be immutable and compared based on state
  #   (rather than identity, as is typically the case)
  #
  # @example
  #   class GeoLocation
  #     include Virtus::ValueObject
  #     attribute :latitude,  Float
  #     attribute :longitude, Float
  #   end
  #
  #   location = GeoLocation.new(:latitude => 10, :longitude => 100)
  #   same_location = GeoLocation.new(:latitude => 10, :longitude => 100)
  #   location == same_location       #=> true
  #   hash = { location => :foo }
  #   hash[same_location]             #=> :foo
  module ValueObject

    # Callback to configure including Class as a Value Object
    #
    # Including Class will include Virtus and have additional
    #   value object semantics defined in this module
    #
    # @return [Undefined]
    #
    # TODO: stacking modules is getting painful
    #   time for Facets' module_inheritance, ActiveSupport::Concern or the like
    #
    # @api private
    def self.included(base)
      Virtus.warn "Virtus::ValueObject is deprecated and will be removed in 1.0.0 #{caller.first}"

      base.instance_eval do
        include Virtus
        include InstanceMethods
        extend ClassMethods
        extend AllowedWriterMethods
        private :attributes=
      end
    end

    private_class_method :included

    module InstanceMethods

      # ValueObjects are immutable and can't be cloned
      #
      # They always represent the same value
      #
      # @example
      #
      #   value_object.clone === value_object # => true
      #
      # @return [self]
      #
      # @api public
      def clone
        self
      end
      alias dup clone

      # Create a new ValueObject by combining the passed attribute hash with
      # the instances attributes.
      #
      # @example
      #
      #   number = PhoneNumber.new(kind: "mobile", number: "123-456-78-90")
      #   number.with(number: "987-654-32-10")
      #   # => #<PhoneNumber kind="mobile" number="987-654-32-10">
      #
      # @return [Object]
      #
      # @api public
      def with(attribute_updates)
        self.class.new(attribute_set.get(self).merge(attribute_updates))
      end

    end

    module AllowedWriterMethods
      # The list of writer methods that can be mass-assigned to in #attributes=
      #
      # @return [Set]
      #
      # @api private
      def allowed_writer_methods
        @allowed_writer_methods ||=
          begin
            allowed_writer_methods = super
            allowed_writer_methods += attribute_set.map{|attr| "#{attr.name}="}
            allowed_writer_methods.to_set.freeze
          end
      end
    end

    module ClassMethods

      # Define an attribute on the receiver
      #
      # The Attribute will have private writer methods (eg., immutable instances)
      #   and be used in equality/equivalence comparisons
      #
      # @example
      #   class GeoLocation
      #     include Virtus::ValueObject
      #
      #     attribute :latitude,  Float
      #     attribute :longitude, Float
      #   end
      #
      # @see Virtus::ClassMethods.attribute
      #
      # @return [self]
      #
      # @api public
      def attribute(name, type, options = {})
        equalizer << name
        super name, type, options.merge(:writer => :private)
      end

      # Define and include a module that provides Value Object semantics
      #
      # Included module will have #inspect, #eql?, #== and #hash
      # methods whose definition is based on the _keys_ argument
      #
      # @example
      #   virtus_class.equalizer
      #
      # @return [Equalizer]
      #   An Equalizer module which defines #inspect, #eql?, #== and #hash
      #   for instances of this class
      #
      # @api public
      def equalizer
        @equalizer ||=
          begin
            equalizer = Virtus::Equalizer.new(name || inspect)
            include equalizer
            equalizer
          end
      end

    end # module ClassMethods

  end # module ValueObject

end # module Virtus