value 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 668d28c3bd798b898afcc5b782521df80f2a9e5b
4
+ data.tar.gz: 745278ed7c2d47b1629f7d50cc0fbade7ab424ea
5
+ SHA512:
6
+ metadata.gz: 5784be59177957109238bdc88aa732d1917e8d633e20cebddba6fe873466b49a92a405a0d5f052d1e3b170722a4c2773d590f9a755f71fde2b3caec142346939
7
+ data.tar.gz: 8331cbb10dff97c705408169cae3dcf938a6bb52642ca1baa02fb4f7477765a8b5f4b60bc1dffe5033921681138c3672aa0d4c4b964e97a2662794ff1964c825
data/README CHANGED
@@ -1,3 +1,165 @@
1
1
  Value
2
2
 
3
- Value is a library for defining value objects in Ruby.
3
+ Value is a library for defining immutable value objects in Ruby. A value
4
+ object is an object whose equality to other objects is determined by its
5
+ value, not its identity, think dates and amounts of money. A value object
6
+ should also be immutable, as you don’t want the date “2013-04-22” itself to
7
+ change but the current date to change from “2013-04-22” to “2013-04-23”.
8
+ That is, you don’t want entries in a calendar for 2013-04-22 to move to
9
+ 2013-04-23 simply because the current date changes from 2013-04-22 to
10
+ 2013-04-23.
11
+
12
+ A value object consists of one or more attributes stored in instance
13
+ variables. Value sets up an #initialize method for you that let’s you set
14
+ these attributes, as, value objects being immutable, this’ll be your only
15
+ chance to do so. Value also adds equality checks ‹#==› and ‹#eql?› (which
16
+ are themselves equivalent), a ‹#hash› method, a nice ‹#inspect› method, and a
17
+ protected attribute reader for each attribute. You may of course add any
18
+ additional methods that your value object will benefit from.
19
+
20
+ That’s basically all there’s too it. Let’s now look at using the Value
21
+ library.
22
+
23
+ § Usage
24
+
25
+ You create value object class by invoking ‹#Value› inside the class
26
+ (module) you wish to make into a value object class. Let’s create a class
27
+ that represent points on a plane:
28
+
29
+ class Point
30
+ Value :x, :y
31
+ end
32
+
33
+ A ‹Point› is thus a value object consisting of two sub-values ‹x› and ‹y›
34
+ (the coordinates). Just from invoking ‹#Value›, a ‹Point› object will have
35
+ a constructor that takes two arguments to set instance variables ‹@x› and
36
+ ‹@y›, equality checks ‹#==› and ‹#eql?› (which are the same), a ‹#hash›
37
+ method, a nice ‹#inspect› method, and two protected attribute readers ‹#x›
38
+ and ‹#y›. We can thus already creat ‹Point›s:
39
+
40
+ origo = Point.new(0, 0)
41
+
42
+ The default of making the attribute readers protected is often good
43
+ practice, but for a ‹Point› it probably makes sense to be able to access
44
+ its coordinates:
45
+
46
+ class Point
47
+ public(*attributes)
48
+ end
49
+
50
+ This’ll make all attributes of ‹Point› public. You can of course choose to
51
+ only make certain attributes public:
52
+
53
+ class Point
54
+ public :x
55
+ end
56
+
57
+ Note that this public is standard Ruby functionality. Adding a method to
58
+ ‹Point› is of course also possible and very much Rubyish:
59
+
60
+ class Point
61
+ def distance(other)
62
+ Math.sqrt((other.x - x)**2 + (other.y - y)**2)
63
+ end
64
+ end
65
+
66
+ For some value object classes you might want to support optional
67
+ attributes. This is done by providing a default value for the attribute,
68
+ like so:
69
+
70
+ class Money
71
+ Value :amount, [:currency, :USD]
72
+ end
73
+
74
+ Here, the ‹currency› attribute will default to ‹:USD›. You can create
75
+ ‹Money› via
76
+
77
+ dollars = Money.new(2)
78
+
79
+ but also
80
+
81
+ kronor = Money.new(2, :SEK)
82
+
83
+ All required attributes must come before any optional attributes.
84
+
85
+ Splat attributes are also supported:
86
+
87
+ class List
88
+ Value :'*elements'
89
+ end
90
+
91
+ empty = List.new
92
+ suits = List.new(:spades, :hearts, :diamonds, :clubs)
93
+
94
+ Splat attributes are optional.
95
+
96
+ Finally, block attributes are also available:
97
+
98
+ class Block
99
+ Value :'&block'
100
+ end
101
+
102
+ block = Block.new{ |e| e * 2 }
103
+
104
+ Block attributes are optional.
105
+
106
+ Comparison beyond ‹#==› is possible by specifingy the ‹:comparable› option
107
+ to ‹#Value›, listing one or more attributes that should be included in the
108
+ comparison:
109
+
110
+ class Vector
111
+ Value :a, :b, :comparable => :a
112
+ end
113
+
114
+ Note that equality (‹#==› and ‹#eql?›) is always defined based on all
115
+ attributes, regardless of arguments to ‹:comparable›.
116
+
117
+ Here we say that comparisons between ‹Vector›s should be made between the
118
+ values of the ‹a› attribute only. We can also make comparisons between all
119
+ attributes of a value object:
120
+
121
+ class Vector
122
+ Value :a, :b, :comparable => true
123
+ end
124
+
125
+ To sum things up, let’s use all possible arguments to ‹#Value› at once:
126
+
127
+ class Method
128
+ Value :file, :line, [:name, 'unnamed'], :'*args', :'&block',
129
+ :comparable => [:file, :line]
130
+ end
131
+
132
+ A ‹Method› consists of file and line information, a possible name, some
133
+ arguments, possibly a block, and is comparable on the file and line on
134
+ which they appear.
135
+
136
+ Check out the {full API documentation}¹ for a more explicit description,
137
+ should you need it or should you want to extend it.
138
+
139
+ ¹ http://disu.se/software/value/api/
140
+
141
+ § Financing
142
+
143
+ Currently, most of my time is spent at my day job and in my rather busy
144
+ private life. Please motivate me to spend time on this piece of software
145
+ by donating some of your money to this project. Yeah, I realize that
146
+ requesting money to develop software is a bit, well, capitalistic of me.
147
+ But please realize that I live in a capitalistic society and I need money
148
+ to have other people give me the things that I need to continue living
149
+ under the rules of said society. So, if you feel that this piece of
150
+ software has helped you out enough to warrant a reward, please PayPal a
151
+ donation to now@disu.se¹. Thanks! Your support won’t go unnoticed!
152
+
153
+ ¹ Send a donation:
154
+ https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=now%40disu%2ese&item_name=Nikolai%20Weibull%20Software%20Services
155
+
156
+ § Reporting Bugs
157
+
158
+ Please report any bugs that you encounter to the {issue tracker}¹.
159
+
160
+ ¹ See https://github.com/now/value/issues
161
+
162
+ § Authors
163
+
164
+ Nikolai Weibull wrote the code, the tests, the manual pages, and this
165
+ README.
data/Rakefile CHANGED
@@ -1,13 +1,22 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- require 'inventory/rake-1.0'
4
- require 'lookout/rake-3.0'
3
+ require 'inventory-rake-1.0'
5
4
 
6
- load File.expand_path('../lib/value/version.rb', __FILE__)
5
+ load File.expand_path('../lib/value-1.0/version.rb', __FILE__)
7
6
 
8
7
  Inventory::Rake::Tasks.define Value::Version, :gem => proc{ |_, s|
9
8
  s.author = 'Nikolai Weibull'
10
9
  s.email = 'now@bitwi.se'
11
10
  s.homepage = 'https://github.com/now/value'
12
11
  }
13
- Lookout::Rake::Tasks::Test.new
12
+
13
+ Inventory::Rake::Tasks.unless_installing_dependencies do
14
+ require 'lookout-rake-3.0'
15
+ Lookout::Rake::Tasks::Test.new
16
+
17
+ require 'inventory-rake-tasks-yard-1.0'
18
+ Inventory::Rake::Tasks::YARD.new do |t|
19
+ t.options += %w'--plugin yard-heuristics-1.0'
20
+ t.globals[:source_code_url] = 'https://github.com/now/%s/blob/v%s/%%s#L%%d' % [t.inventory.package, t.inventory]
21
+ end
22
+ end
@@ -1,71 +1,134 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ # Represents an immutable [value
4
+ # object](http://en.wikipedia.org/wiki/Value_object) with an {#initialize}
5
+ # method, equality checks {#==} and {#eql?}, a {#hash} function, and an
6
+ # {#inspect} method.
7
+ #
8
+ # @example A Point Value Object
9
+ # class Point
10
+ # Value :x, :y
11
+ # end
12
+ #
13
+ # @example A Point Value Object with Public Accessors
14
+ # class Point
15
+ # Value :x, :y
16
+ # public(*attributes)
17
+ # end
18
+ #
19
+ # @example A Value Object with Optional Attributes
20
+ # class Money
21
+ # Value :amount, [:currency, :USD]
22
+ # end
23
+ #
24
+ # @example A Value Object with a Comparable Attribute
25
+ # class Vector
26
+ # Value :a, :b, :comparable => :a
27
+ # end
3
28
  module Value
29
+ # Creates a new value object, using ARGUMENTS to set up the value’s
30
+ # {Attributes#required required}, {Attributes#optional optional}, and
31
+ # {Attributes#splat splat} attributes, and the given block, if given,
32
+ # if a {Attributes#block block} is desired.
33
+ #
34
+ # @param [Object, …] arguments
35
+ # @raise [ArgumentError] If ARGUMENTS#length is less than the number of
36
+ # {Attributes#required required} attributes
37
+ # @raise [ArgumentError] If ARGUMENTS#length is greater than the number of
38
+ # {Attributes#required required} and {Attributes#optional optional}
39
+ # attributes
40
+ # @yield [?]
4
41
  def initialize(*arguments)
5
42
  raise ArgumentError,
6
43
  'wrong number of arguments (%d for %d)' %
7
- [arguments.length, values.required.length] if
8
- arguments.length < values.required.length
44
+ [arguments.length, attributes.required.length] if
45
+ arguments.length < attributes.required.length
9
46
  raise ArgumentError,
10
47
  'wrong number of arguments (%d for %d)' %
11
48
  [arguments.length,
12
- values.required.length + values.optional.length] if
13
- arguments.length > values.required.length +
14
- values.optional.length and not values.splat
15
- instance_variable_set :"@#{values.block}",
16
- block_given? ? Proc.new : nil if values.block
17
- instance_variable_set :"@#{values.splat}",
18
- arguments[values.required.length +
19
- values.optional.length..-1] if values.splat
20
- values.required.each_with_index do |value, index|
49
+ attributes.required.length + attributes.optional.length] if
50
+ arguments.length > attributes.required.length +
51
+ attributes.optional.length and not attributes.splat
52
+ instance_variable_set :"@#{attributes.block}",
53
+ block_given? ? Proc.new : nil if attributes.block
54
+ instance_variable_set :"@#{attributes.splat}",
55
+ arguments[attributes.required.length +
56
+ attributes.optional.length..-1] if attributes.splat
57
+ attributes.required.each_with_index do |value, index|
21
58
  instance_variable_set :"@#{value}", arguments[index]
22
59
  end
23
- values.optional.each_with_index do |(value, default), index|
24
- offset = values.required.length + index
60
+ attributes.optional.each_with_index do |(value, default), index|
61
+ offset = attributes.required.length + index
25
62
  instance_variable_set :"@#{value}",
26
63
  offset >= arguments.length ? default : arguments[offset]
27
64
  end
28
65
  end
29
66
 
67
+ # @return [Boolean] True if the receiver’s class and all of its {Attributes}
68
+ # `#==` those of OTHER
30
69
  def ==(other)
31
- self.class == other.class and
32
- values.all?{ |value| send(value) == other.send(value) }
70
+ self.class == other.class and attributes.all?{ |e| send(e) == other.send(e) }
33
71
  end
34
72
 
73
+ # (see #==)
35
74
  alias eql? ==
36
75
 
76
+ # @return [Fixnum] The hash value of the receiver’s class XORed with the hash
77
+ # value of the Array of the values of the {Attributes} of the receiver
37
78
  def hash
38
- self.class.hash ^ values.map{ |value| send(value) }.hash
79
+ self.class.hash ^ attributes.map{ |e| send(e) }.hash
39
80
  end
40
81
 
82
+ # @return [String] The inspection of the receiver
41
83
  def inspect
42
84
  '%s.new(%s)' %
43
85
  [self.class,
44
- [values.required.map{ |name| send(name).inspect },
45
- values.optional.
86
+ [attributes.required.map{ |name| send(name).inspect },
87
+ attributes.optional.
46
88
  map{ |name, default| [send(name), default] }.
47
- select{ |value, default| value != default or values.splat }.
89
+ select{ |value, default| value != default or attributes.splat }.
48
90
  map{ |value, _| value },
49
- values.splat ? send(values.splat).map{ |value| value.inspect } : [],
50
- values.block ? ['&%s' % [send(values.block).inspect]] : []].
91
+ attributes.splat ? send(attributes.splat).map{ |value| value.inspect } : [],
92
+ attributes.block ? ['&%s' % [send(attributes.block).inspect]] : []].
51
93
  flatten.join(', ')]
52
94
  end
53
95
 
54
- load File.expand_path('../value/version.rb', __FILE__)
96
+ private
97
+
98
+ def values
99
+ warn "%s#%s: deprecated, use #attributes" % [self.class, __method__]
100
+ attributes
101
+ end
102
+
103
+ load File.expand_path('../value-1.0/version.rb', __FILE__)
55
104
  Version.load
56
105
  end
57
106
 
107
+ # Ruby’s Module class, extended to add {Module#Value #Value}, a way of creating
108
+ # value object classes.
58
109
  class Module
110
+ # @overload Value(first, *rest, options = {})
111
+ #
112
+ # Marks the module as a {::Value} object with attributes FIRST and REST.
113
+ # This’ll add protected attribute readers for each of these attributes and
114
+ # a private method #attributes that returns the {Value::Attributes}
115
+ # themselves, include {Value::Comparable} if COMPARABLE isn’t falsy, and
116
+ # finally include {::Value}.
117
+ #
118
+ # @param (see ::Value::Attributes#initialize)
119
+ # @option (see ::Value::Attributes#initialize)
120
+ # @raise (see ::Value::Attributes#initialize)
59
121
  def Value(first, *rest)
60
122
  options = Hash === rest.last ? rest.pop : {}
61
- values = Value::Values.new(*([first] + rest).push({:comparable => options[:comparable]}))
62
- attr_reader(*values)
63
- protected(*values)
64
- define_method :values do
65
- values
123
+ attributes = Value::Attributes.new(first, *rest.dup.push({:comparable => options[:comparable]}))
124
+ attr_reader(*attributes)
125
+ protected(*attributes)
126
+ define_method :attributes do
127
+ attributes
66
128
  end
67
- private :values
129
+ private :attributes
68
130
  include Value::Comparable if options[:comparable]
69
131
  include Value
132
+ self
70
133
  end
71
134
  end
@@ -0,0 +1,102 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Keeps track of the structure of the attributes associated with a {Value}
4
+ # object. This is an ordered set of {#required}, {#optional}, {#splat}, and
5
+ # {#block} arguments to the value object’s {Value#initialize initialize}
6
+ # method, some or all of which may be {#comparable}.
7
+ class Value::Attributes
8
+ include Enumerable
9
+
10
+ # @overload initialize(first, *rest, options = {})
11
+ #
12
+ # Sets up a {Value} objects attributes FIRST and REST. If the last
13
+ # attribute starts with ‘&’, then the value object’s {Value#initialize
14
+ # initialize} method may take an optional block. If the last or second to
15
+ # last, depending on the previous sentence, attribute starts with ‘*’, then
16
+ # the value object’s {Value#initialize initialize} method may take a splat
17
+ # argument. The rest of the attributes are split at the first element
18
+ # that’s an Array. All attributes before this element are required
19
+ # arguments to the value object’s {Value#initialize initialize} method.
20
+ # The first element that’s an Array and all following elements, which
21
+ # should also be Arrays, are optional arguments to the value object’s
22
+ # {Value#initialize initialize} method, where the first element of these
23
+ # Arrays is the attribute and the last element is its default value.
24
+ #
25
+ # @param [Symbol, Array<Symbol, Object>] first
26
+ # @param [Array<Symbol, Array<Symbol, Object>>] rest
27
+ # @param [Hash] options
28
+ # @option options [Array<Symbol>, Boolean, nil] :comparable (nil) The
29
+ # subset of first and rest that should be used for comparing instances of
30
+ # this value object class, or a truthy value to use the whole set, or a
31
+ # falsy value to use an empty set
32
+ # @raise [ArgumentError] If any element of COMPARABLE isn’t an element of
33
+ # first and rest
34
+ def initialize(first, *rest)
35
+ options = Hash === rest.last ? rest.pop : {}
36
+ names = [first] + rest
37
+ names.pop if @block = names.last.to_s.start_with?('&') ?
38
+ names.last.to_s[1..-1].to_sym : nil
39
+ names.pop if @splat = (names.last and names.last.to_s.start_with?('*')) ?
40
+ names.last.to_s[1..-1].to_sym : nil
41
+ @required = names.take_while{ |e| not(Array === e) }.map(&:to_sym)
42
+ @optional = names[required.length..-1].map{ |e| [e.first.to_sym, e.last] }
43
+ all = to_a
44
+ @comparable =
45
+ case options[:comparable]
46
+ when Array
47
+ options[:comparable].each{ |e|
48
+ raise ArgumentError, '%p is not among comparable members %s' %
49
+ [e, all.map(&:inspect).join(', ')] unless all.include?(e)
50
+ }
51
+ when false, nil
52
+ []
53
+ else
54
+ all
55
+ end
56
+ end
57
+
58
+ # @overload
59
+ # Enumerates the attributes.
60
+ #
61
+ # @yieldparam [Symbol] attribute
62
+ # @overload
63
+ # @return [Enumerator<Symbol>] An Enumerator over the attributes
64
+ def each
65
+ return enum_for(__method__) unless block_given?
66
+ required.each do |name|
67
+ yield name
68
+ end
69
+ optional.each do |name, _|
70
+ yield name
71
+ end
72
+ yield splat if splat
73
+ yield block if block
74
+ self
75
+ end
76
+
77
+ # @return [Boolean] True if the receiver’s class and its required, optional,
78
+ # splat, and block attributes `#==` those of OTHER
79
+ def ==(other)
80
+ self.class == other.class and
81
+ required == other.required and
82
+ optional == other.optional and
83
+ splat == other.splat and
84
+ block == other.block
85
+ end
86
+
87
+ # @return [Array<Symbol>] The required attributes
88
+ attr_reader :required
89
+
90
+ # @return [Array<Array<Symbol, Object>>] The optional attributes and their
91
+ # defaults
92
+ attr_reader :optional
93
+
94
+ # @return [Symbol, nil] The splat attribute
95
+ attr_reader :splat
96
+
97
+ # @return [Symbol, nil] The block attribute
98
+ attr_reader :block
99
+
100
+ # @return [Array<Symbol>] The comparable attributes
101
+ attr_reader :comparable
102
+ end
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Module included by {Module#Value} when passed the :comparable option.
4
+ module Value::Comparable
5
+ include ::Comparable
6
+
7
+ # @return [Integer, nil] The first non-zero comparison of the
8
+ # {Attributes#comparable} values of the receiver with those of OTHER (zero
9
+ # if they’re all equal), or nil if the #class of the receiver doesn’t `#==`
10
+ # that of OTHER
11
+ def <=>(other)
12
+ return nil unless self.class == other.class
13
+ v = nil
14
+ (attributes.comparable.any?{ |e| v = (send(e) <=> other.send(e)).nonzero? } and v) or 0
15
+ end
16
+
17
+ # (see #==)
18
+ alias eql? ==
19
+ end
@@ -3,21 +3,20 @@
3
3
  require 'inventory-1.0'
4
4
 
5
5
  module Value
6
- Version = Inventory.new(1, 1, 1){
6
+ Version = Inventory.new(1, 1, 2){
7
7
  def dependencies
8
8
  super + Inventory::Dependencies.new{
9
- development 'inventory-rake', 1, 2, 0
9
+ development 'inventory-rake', 1, 4, 0
10
10
  development 'lookout', 3, 0, 0
11
11
  development 'lookout-rake', 3, 0, 0
12
- development 'yard', 0, 7, 0
12
+ development 'yard', 0, 8, 0
13
+ development 'yard-heuristics', 1, 1, 0
13
14
  }
14
15
  end
15
16
 
16
- def libs
17
- %w'
18
- value/comparable.rb
19
- value/values.rb
20
- '
17
+ def package_libs
18
+ %w[attributes.rb
19
+ comparable.rb]
21
20
  end
22
21
  }
23
22
  end
metadata CHANGED
@@ -1,86 +1,278 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: value
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
5
- prerelease:
4
+ version: 1.1.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Nikolai Weibull
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-05-04 00:00:00.000000000 Z
11
+ date: 2013-04-30 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: inventory
16
- requirement: &16034964 !ruby/object:Gem::Requirement
17
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
21
- version: '1.3'
19
+ version: '1.4'
22
20
  type: :runtime
23
21
  prerelease: false
24
- version_requirements: *16034964
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
25
27
  - !ruby/object:Gem::Dependency
26
28
  name: inventory-rake
27
- requirement: &16034472 !ruby/object:Gem::Requirement
28
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
31
  - - ~>
31
32
  - !ruby/object:Gem::Version
32
- version: '1.2'
33
+ version: '1.4'
33
34
  type: :development
34
35
  prerelease: false
35
- version_requirements: *16034472
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
36
41
  - !ruby/object:Gem::Dependency
37
42
  name: lookout
38
- requirement: &16034004 !ruby/object:Gem::Requirement
39
- none: false
43
+ requirement: !ruby/object:Gem::Requirement
40
44
  requirements:
41
45
  - - ~>
42
46
  - !ruby/object:Gem::Version
43
47
  version: '3.0'
44
48
  type: :development
45
49
  prerelease: false
46
- version_requirements: *16034004
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
47
55
  - !ruby/object:Gem::Dependency
48
56
  name: lookout-rake
49
- requirement: &16049868 !ruby/object:Gem::Requirement
50
- none: false
57
+ requirement: !ruby/object:Gem::Requirement
51
58
  requirements:
52
59
  - - ~>
53
60
  - !ruby/object:Gem::Version
54
61
  version: '3.0'
55
62
  type: :development
56
63
  prerelease: false
57
- version_requirements: *16049868
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: yard
60
- requirement: &16049400 !ruby/object:Gem::Requirement
61
- none: false
71
+ requirement: !ruby/object:Gem::Requirement
62
72
  requirements:
63
73
  - - ~>
64
74
  - !ruby/object:Gem::Version
65
- version: 0.7.0
75
+ version: 0.8.0
66
76
  type: :development
67
77
  prerelease: false
68
- version_requirements: *16049400
69
- description: ! " Value\n\n Value is a library
70
- for defining value objects in Ruby.\n"
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 0.8.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard-heuristics
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.1'
97
+ description: |2
98
+ Value
99
+
100
+ Value is a library for defining immutable value objects in Ruby. A value
101
+ object is an object whose equality to other objects is determined by its
102
+ value, not its identity, think dates and amounts of money. A value object
103
+ should also be immutable, as you don’t want the date “2013-04-22” itself to
104
+ change but the current date to change from “2013-04-22” to “2013-04-23”.
105
+ That is, you don’t want entries in a calendar for 2013-04-22 to move to
106
+ 2013-04-23 simply because the current date changes from 2013-04-22 to
107
+ 2013-04-23.
108
+
109
+ A value object consists of one or more attributes stored in instance
110
+ variables. Value sets up an #initialize method for you that let’s you set
111
+ these attributes, as, value objects being immutable, this’ll be your only
112
+ chance to do so. Value also adds equality checks ‹#==› and ‹#eql?› (which
113
+ are themselves equivalent), a ‹#hash› method, a nice ‹#inspect› method, and a
114
+ protected attribute reader for each attribute. You may of course add any
115
+ additional methods that your value object will benefit from.
116
+
117
+ That’s basically all there’s too it. Let’s now look at using the Value
118
+ library.
119
+
120
+ § Usage
121
+
122
+ You create value object class by invoking ‹#Value› inside the class
123
+ (module) you wish to make into a value object class. Let’s create a class
124
+ that represent points on a plane:
125
+
126
+ class Point
127
+ Value :x, :y
128
+ end
129
+
130
+ A ‹Point› is thus a value object consisting of two sub-values ‹x› and ‹y›
131
+ (the coordinates). Just from invoking ‹#Value›, a ‹Point› object will have
132
+ a constructor that takes two arguments to set instance variables ‹@x› and
133
+ ‹@y›, equality checks ‹#==› and ‹#eql?› (which are the same), a ‹#hash›
134
+ method, a nice ‹#inspect› method, and two protected attribute readers ‹#x›
135
+ and ‹#y›. We can thus already creat ‹Point›s:
136
+
137
+ origo = Point.new(0, 0)
138
+
139
+ The default of making the attribute readers protected is often good
140
+ practice, but for a ‹Point› it probably makes sense to be able to access
141
+ its coordinates:
142
+
143
+ class Point
144
+ public(*attributes)
145
+ end
146
+
147
+ This’ll make all attributes of ‹Point› public. You can of course choose to
148
+ only make certain attributes public:
149
+
150
+ class Point
151
+ public :x
152
+ end
153
+
154
+ Note that this public is standard Ruby functionality. Adding a method to
155
+ ‹Point› is of course also possible and very much Rubyish:
156
+
157
+ class Point
158
+ def distance(other)
159
+ Math.sqrt((other.x - x)**2 + (other.y - y)**2)
160
+ end
161
+ end
162
+
163
+ For some value object classes you might want to support optional
164
+ attributes. This is done by providing a default value for the attribute,
165
+ like so:
166
+
167
+ class Money
168
+ Value :amount, [:currency, :USD]
169
+ end
170
+
171
+ Here, the ‹currency› attribute will default to ‹:USD›. You can create
172
+ ‹Money› via
173
+
174
+ dollars = Money.new(2)
175
+
176
+ but also
177
+
178
+ kronor = Money.new(2, :SEK)
179
+
180
+ All required attributes must come before any optional attributes.
181
+
182
+ Splat attributes are also supported:
183
+
184
+ class List
185
+ Value :'*elements'
186
+ end
187
+
188
+ empty = List.new
189
+ suits = List.new(:spades, :hearts, :diamonds, :clubs)
190
+
191
+ Splat attributes are optional.
192
+
193
+ Finally, block attributes are also available:
194
+
195
+ class Block
196
+ Value :'&block'
197
+ end
198
+
199
+ block = Block.new{ |e| e * 2 }
200
+
201
+ Block attributes are optional.
202
+
203
+ Comparison beyond ‹#==› is possible by specifingy the ‹:comparable› option
204
+ to ‹#Value›, listing one or more attributes that should be included in the
205
+ comparison:
206
+
207
+ class Vector
208
+ Value :a, :b, :comparable => :a
209
+ end
210
+
211
+ Note that equality (‹#==› and ‹#eql?›) is always defined based on all
212
+ attributes, regardless of arguments to ‹:comparable›.
213
+
214
+ Here we say that comparisons between ‹Vector›s should be made between the
215
+ values of the ‹a› attribute only. We can also make comparisons between all
216
+ attributes of a value object:
217
+
218
+ class Vector
219
+ Value :a, :b, :comparable => true
220
+ end
221
+
222
+ To sum things up, let’s use all possible arguments to ‹#Value› at once:
223
+
224
+ class Method
225
+ Value :file, :line, [:name, 'unnamed'], :'*args', :'&block',
226
+ :comparable => [:file, :line]
227
+ end
228
+
229
+ A ‹Method› consists of file and line information, a possible name, some
230
+ arguments, possibly a block, and is comparable on the file and line on
231
+ which they appear.
232
+
233
+ Check out the {full API documentation}¹ for a more explicit description,
234
+ should you need it or should you want to extend it.
235
+
236
+ ¹ http://disu.se/software/value/api/
237
+
238
+ § Financing
239
+
240
+ Currently, most of my time is spent at my day job and in my rather busy
241
+ private life. Please motivate me to spend time on this piece of software
242
+ by donating some of your money to this project. Yeah, I realize that
243
+ requesting money to develop software is a bit, well, capitalistic of me.
244
+ But please realize that I live in a capitalistic society and I need money
245
+ to have other people give me the things that I need to continue living
246
+ under the rules of said society. So, if you feel that this piece of
247
+ software has helped you out enough to warrant a reward, please PayPal a
248
+ donation to now@disu.se¹. Thanks! Your support won’t go unnoticed!
249
+
250
+ ¹ Send a donation:
251
+ https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=now%40disu%2ese&item_name=Nikolai%20Weibull%20Software%20Services
252
+
253
+ § Reporting Bugs
254
+
255
+ Please report any bugs that you encounter to the {issue tracker}¹.
256
+
257
+ ¹ See https://github.com/now/value/issues
258
+
259
+ § Authors
260
+
261
+ Nikolai Weibull wrote the code, the tests, the manual pages, and this
262
+ README.
71
263
  email: now@bitwi.se
72
264
  executables: []
73
265
  extensions: []
74
266
  extra_rdoc_files: []
75
267
  files:
76
- - lib/value/comparable.rb
77
- - lib/value/values.rb
268
+ - lib/value-1.0/attributes.rb
269
+ - lib/value-1.0/comparable.rb
78
270
  - lib/value-1.0.rb
79
- - lib/value/version.rb
80
- - test/unit/value/comparable.rb
81
- - test/unit/value/values.rb
271
+ - lib/value-1.0/version.rb
272
+ - test/unit/value-1.0/attributes.rb
273
+ - test/unit/value-1.0/comparable.rb
82
274
  - test/unit/value-1.0.rb
83
- - test/unit/value/version.rb
275
+ - test/unit/value-1.0/version.rb
84
276
  - README
85
277
  - Rakefile
86
278
  homepage: https://github.com/now/value
@@ -91,21 +283,19 @@ rdoc_options: []
91
283
  require_paths:
92
284
  - lib
93
285
  required_ruby_version: !ruby/object:Gem::Requirement
94
- none: false
95
286
  requirements:
96
- - - ! '>='
287
+ - - '>='
97
288
  - !ruby/object:Gem::Version
98
289
  version: '0'
99
290
  required_rubygems_version: !ruby/object:Gem::Requirement
100
- none: false
101
291
  requirements:
102
- - - ! '>='
292
+ - - '>='
103
293
  - !ruby/object:Gem::Version
104
294
  version: '0'
105
295
  requirements: []
106
296
  rubyforge_project:
107
- rubygems_version: 1.8.10
297
+ rubygems_version: 2.0.0
108
298
  signing_key:
109
299
  specification_version: 4
110
- summary: Value is a library for defining value objects in Ruby.
300
+ summary: Value is a library for defining immutable value objects in Ruby.
111
301
  test_files: []
@@ -1,13 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- module Value::Comparable
4
- include Comparable
5
-
6
- def <=>(other)
7
- return nil unless self.class == other.class
8
- v = nil
9
- (values.comparable.any?{ |e| v = (send(e) <=> other.send(e)).nonzero? } and v) or 0
10
- end
11
-
12
- alias eql? ==
13
- end
@@ -1,44 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- class Value::Values
4
- include Enumerable
5
-
6
- def initialize(*values)
7
- options = Hash === values.last ? values.pop : {}
8
- values.pop if @block = values.last.to_s.start_with?('&') ? values.last.to_s[1..-1].to_sym : nil
9
- values.pop if @splat = (values.last and values.last.to_s.start_with?('*')) ?
10
- values.last.to_s[1..-1].to_sym : nil
11
- required, optional = values.partition{ |e| not(Array === e) }
12
- @required = required.map(&:to_sym)
13
- @optional = optional.map{ |e| [e.first.to_sym, e.last] }
14
- all = to_a
15
- @comparable = Array === options[:comparable] ?
16
- options[:comparable].each{ |e|
17
- raise ArgumentError, '%p is not among comparable members %s' % [e, all.map(&:inspect).join(', ')] unless all.include?(e)
18
- } :
19
- all
20
- end
21
-
22
- def each
23
- return enum_for(__method__) unless block_given?
24
- required.each do |value|
25
- yield value
26
- end
27
- optional.each do |value, _|
28
- yield value
29
- end
30
- yield splat if splat
31
- yield block if block
32
- self
33
- end
34
-
35
- def ==(other)
36
- self.class == other.class and
37
- required == other.required and
38
- optional == other.optional and
39
- splat == other.splat and
40
- block == other.block
41
- end
42
-
43
- attr_reader :required, :optional, :splat, :block, :comparable
44
- end