value 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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