attr_comparable 0.0.1
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.
- data/.gitignore +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +18 -0
- data/README.md +75 -0
- data/Rakefile +9 -0
- data/attr_comparable.gemspec +18 -0
- data/lib/attr_comparable.rb +44 -0
- data/lib/attr_comparable/version.rb +3 -0
- data/test/attr_comparable_test.rb +107 -0
- metadata +95 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.idea
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
attr_comparable gem
|
2
|
+
===================
|
3
|
+
|
4
|
+
Mix-in to make a class Comparable, declaratively
|
5
|
+
------------------------------------------------
|
6
|
+
|
7
|
+
Use `attr_compare <attribute list>`
|
8
|
+
to declare the attributes which should be compared, in their order of precedence.
|
9
|
+
Attributes may be nil. nil attributes sort earlier than non-nil to match the SQL behavior for NULL.
|
10
|
+
|
11
|
+
Example without AttrComparable
|
12
|
+
------------------------------
|
13
|
+
|
14
|
+
Consider this value class that holds full names:
|
15
|
+
```ruby
|
16
|
+
class FullName
|
17
|
+
include Comparable
|
18
|
+
|
19
|
+
attr_reader :first, :middle, :last, :suffix
|
20
|
+
|
21
|
+
def initialize(first, middle, last, suffix = nil)
|
22
|
+
@first = first
|
23
|
+
@middle = middle
|
24
|
+
@last = last
|
25
|
+
@suffix = suffix
|
26
|
+
end
|
27
|
+
|
28
|
+
def <=>(other)
|
29
|
+
(last <=> other.last).nonzero? ||
|
30
|
+
(first <=> other.first).nonzero? ||
|
31
|
+
(middle <=> other.middle).nonzero? ||
|
32
|
+
suffix <=> other.suffix
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
You can see that the `<=>` method isn't very DRY, and as shown it doesn't even work with `nil`.
|
37
|
+
(That's just too ugly to show.)
|
38
|
+
|
39
|
+
Example with AttrComparable
|
40
|
+
---------------------------
|
41
|
+
Here it is using the gem. Only the 2 lines with the comments are needed.
|
42
|
+
```ruby
|
43
|
+
require 'attr_comparable'
|
44
|
+
require 'active_support'
|
45
|
+
|
46
|
+
class FullName
|
47
|
+
include AttrComparable # AttrComparable automatically includes Comparable
|
48
|
+
|
49
|
+
attr_compare :last, :first, :middle, :suffix # will be compared in this precedence order
|
50
|
+
attr_reader :first, :middle, :last, :suffix
|
51
|
+
|
52
|
+
def initialize(first, middle, last, suffix = nil)
|
53
|
+
@first = first
|
54
|
+
@middle = middle
|
55
|
+
@last = last
|
56
|
+
@suffix = suffix
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
no_suffix = [first.presence, middle.presence, last.presence].compact.join(' ')
|
61
|
+
[no_suffix, suffix.presence].compact.join(', ')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
Example Usage
|
66
|
+
---------------------------
|
67
|
+
```ruby
|
68
|
+
>> mom = FullName.new("Kathy", nil, "Doe")
|
69
|
+
>> dad = FullName.new("John", "Q.", "Public")
|
70
|
+
>> junior = FullName.new("John", "Q.", "Public", "Jr.")
|
71
|
+
>> junior > dad
|
72
|
+
=> true
|
73
|
+
>> [mom, dad, junior].sort.map &:to_s
|
74
|
+
=> ["Kathy Doe", "John Q. Public", "John Q. Public, Jr."]
|
75
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path('../lib/attr_comparable/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.add_dependency 'minitest' # Included in Ruby 1.9, but we want the latest.
|
5
|
+
gem.add_development_dependency 'rake', '>=0.9'
|
6
|
+
|
7
|
+
gem.authors = ["Colin Kelley"]
|
8
|
+
gem.email = ["colindkelley@gmail.com"]
|
9
|
+
gem.description = %q{AttrComparable}
|
10
|
+
gem.summary = %q{Mix-in to make a value class Comparable. Simply declare the order of attributes to compare and the <=> (as needed by Comparable) is generated for you, including support for nil. Includes Comparable.}
|
11
|
+
gem.homepage = "https://github.com/RingRevenue/attr_comparable"
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/.*\.rb})
|
16
|
+
gem.name = "attr_comparable"
|
17
|
+
gem.version = AttrComparable::VERSION
|
18
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#
|
2
|
+
# Mix-in to make a class Comparable
|
3
|
+
#
|
4
|
+
# Use attr_comparabie <attribute list>
|
5
|
+
# to declare the attributes which should be compared, and the order they should be
|
6
|
+
# Attributes may be nil; nil attributes sort earlier than non-nil to match the SQL convention
|
7
|
+
#
|
8
|
+
module AttrComparable
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# like <=> but handles nil values
|
13
|
+
# when equal, returns nil rather than 0 so the caller can || together
|
14
|
+
def compare_with_nil(left, right)
|
15
|
+
if left.nil?
|
16
|
+
if right.nil?
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
-1
|
20
|
+
end
|
21
|
+
elsif right.nil?
|
22
|
+
1
|
23
|
+
else
|
24
|
+
(left <=> right).nonzero?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def attr_compare(*attributes)
|
29
|
+
attributes = attributes.flatten
|
30
|
+
class_eval <<-EOS
|
31
|
+
def <=>(rhs)
|
32
|
+
#{attributes.map do |attribute|
|
33
|
+
"self.class.compare_with_nil(self.#{attribute}, rhs.#{attribute})"
|
34
|
+
end.join(" || ")
|
35
|
+
} || 0
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.included(base_class)
|
42
|
+
base_class.extend ClassMethods
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require File.expand_path('../../lib/attr_comparable', __FILE__)
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require(:default)
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
|
7
|
+
class ComparableTestOneParameter
|
8
|
+
include AttrComparable
|
9
|
+
attr_reader :last_name
|
10
|
+
attr_compare :last_name
|
11
|
+
|
12
|
+
def initialize(last_name)
|
13
|
+
@last_name = last_name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ComparableTestManyParameters
|
18
|
+
include AttrComparable
|
19
|
+
attr_reader :last_name, :first_name
|
20
|
+
attr_compare :last_name, :first_name
|
21
|
+
|
22
|
+
def initialize(last_name, first_name)
|
23
|
+
@last_name = last_name
|
24
|
+
@first_name = first_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
describe 'AttrComparable' do
|
30
|
+
describe "one parameter" do
|
31
|
+
before do
|
32
|
+
@d1 = ComparableTestOneParameter.new('Jones')
|
33
|
+
@d2 = ComparableTestOneParameter.new('Jones')
|
34
|
+
@d3 = ComparableTestOneParameter.new('Kelley')
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should define <=>" do
|
38
|
+
assert_equal 0, @d1 <=> @d2
|
39
|
+
assert_equal -1, @d1 <=> @d3
|
40
|
+
assert_equal 1, @d3 <=> @d1
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should define relative operators like Comparable" do
|
44
|
+
assert @d1.is_a?(Comparable)
|
45
|
+
assert @d1 < @d3
|
46
|
+
assert @d3 >= @d2
|
47
|
+
assert @d1 == @d2
|
48
|
+
assert @d2 != @d3
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "many parameters" do
|
53
|
+
before do
|
54
|
+
@d1 = ComparableTestManyParameters.new('Jones', 'S')
|
55
|
+
@d2 = ComparableTestManyParameters.new('Jones', 'T')
|
56
|
+
@d3 = ComparableTestManyParameters.new('Jones', 'S')
|
57
|
+
@d4 = ComparableTestManyParameters.new('Kelley', 'C')
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should define <=>" do
|
61
|
+
assert_equal 0, @d1 <=> @d3
|
62
|
+
assert_equal -1, @d1 <=> @d2
|
63
|
+
assert_equal 1, @d4 <=> @d1
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be Comparable" do
|
67
|
+
assert @d1.is_a?(Comparable)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should define relative operators from Comparable" do
|
71
|
+
assert @d1 < @d2
|
72
|
+
assert @d2 >= @d3
|
73
|
+
assert @d1 == @d3
|
74
|
+
assert @d2 != @d3
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should be able to compare with nil" do
|
79
|
+
assert_equal nil, ComparableTestManyParameters.compare_with_nil(nil, nil)
|
80
|
+
assert_equal -1, ComparableTestManyParameters.compare_with_nil(nil, 1)
|
81
|
+
assert_equal 1, ComparableTestOneParameter.compare_with_nil(1, nil)
|
82
|
+
assert_equal 1, ComparableTestOneParameter.compare_with_nil("dog", "cat")
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "many parameters with nil" do
|
86
|
+
before do # sort order
|
87
|
+
@d1 = ComparableTestManyParameters.new(nil, 'S') # 1
|
88
|
+
@d2 = ComparableTestManyParameters.new('Jones', nil) # 2
|
89
|
+
@d3 = ComparableTestManyParameters.new(nil, nil) # 0
|
90
|
+
@d4 = ComparableTestManyParameters.new('Jones', 'S') # 3
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should define <=> with nil first" do
|
94
|
+
assert_equal 0, @d1 <=> @d1
|
95
|
+
assert_equal 0, @d3 <=> @d3
|
96
|
+
assert_equal -1, @d1 <=> @d2
|
97
|
+
assert_equal 1, @d2 <=> @d3
|
98
|
+
assert_equal -1, @d2 <=> @d4
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should define relative operators from Comparable" do
|
102
|
+
assert @d1 == @d1
|
103
|
+
assert @d2 > @d3
|
104
|
+
assert @d2 != @d3
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attr_comparable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Colin Kelley
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0.9'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0.9'
|
46
|
+
description: AttrComparable
|
47
|
+
email:
|
48
|
+
- colindkelley@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- Gemfile.lock
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- attr_comparable.gemspec
|
59
|
+
- lib/attr_comparable.rb
|
60
|
+
- lib/attr_comparable/version.rb
|
61
|
+
- test/attr_comparable_test.rb
|
62
|
+
homepage: https://github.com/RingRevenue/attr_comparable
|
63
|
+
licenses: []
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
hash: 4162700180279227236
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
hash: 4162700180279227236
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.25
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Mix-in to make a value class Comparable. Simply declare the order of attributes
|
92
|
+
to compare and the <=> (as needed by Comparable) is generated for you, including
|
93
|
+
support for nil. Includes Comparable.
|
94
|
+
test_files:
|
95
|
+
- test/attr_comparable_test.rb
|