unique_by 1.0.0 → 2.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c877eeb4bee389ff8f304f38825a35ab1ce2c398
4
- data.tar.gz: 87108e22b935547a8deb63355979f9fd2a16eef1
3
+ metadata.gz: 4839ad7e8c93eabad52a34ae43774c3a4fa31ead
4
+ data.tar.gz: d205bd87ab905c57bbc620df6400a36fe9c95fba
5
5
  SHA512:
6
- metadata.gz: e5e50dd6ce9bcb170af31c2e171388c2bec13e650685f62cded4a11fefeaf6d9d8d81a3915f37c4254ba002ce80b1fe0f15938da0356fba6b1020ffac2bc4419
7
- data.tar.gz: 328cdd45129518b3e131ecd8e562ae6e216de0f4ad9da433e2b9e3a9a39c6b597c16fd10d1e3c0d368b43c4ca2ade12c68d5e1b9dc4a34377b169de0b50ef6c4
6
+ metadata.gz: afe1110aa03ea678e269655cbbd8d10ff7cbca5406b62a5193724f5617789db734d93e033a56d6077611e3ef9c54ead59dc5a1f80035c74a73ccbcf29cd5deba
7
+ data.tar.gz: fe7ac2951507f1b2a709f905469881f3e29b4c67d3c1a110b3bd8448d82d869932a13a3758c9de217121b296e989ee50366b17f2cea089ef35deeb1b5babefa4
data/README.md CHANGED
@@ -12,7 +12,9 @@ When do you need this?
12
12
 
13
13
  Add this line to your application's Gemfile:
14
14
 
15
- gem 'unique_by'
15
+ ```ruby
16
+ gem 'unique_by'
17
+ ```
16
18
 
17
19
  And then execute:
18
20
 
@@ -28,84 +30,112 @@ Or install it yourself as:
28
30
 
29
31
  You first need to specify a unique group in your model:
30
32
 
31
- # == Schema Info
32
- #
33
- # Table name: medical_bills
34
- #
35
- # id :integer(11) not null, primary key
36
- # client_id :integer(11)
37
- #
33
+ ```ruby
34
+ # == Schema Info
35
+ #
36
+ # Table name: medical_bills
37
+ #
38
+ # id :integer(11) not null, primary key
39
+ # client_id :integer(11)
40
+ #
38
41
 
39
- class MedicalBill < ActiveRecord::Base
40
- unique_by :client_id, total: 50
41
- end
42
+ class MedicalBill < ActiveRecord::Base
43
+ unique_by client_id: 50 # total of 50 clients
44
+ end
45
+ ```
42
46
 
43
47
  then, you can use these basic methods:
44
48
 
45
- bill1 = MedicalBill.find(123) # from a DB shard for client_id = 1
46
- bill2 = MedicalBill.find(123) # from a DB shard for client_id = 2
49
+ ```ruby
50
+ bill1 = MedicalBill.find(123) # from a DB shard for client_id = 1
51
+ bill2 = MedicalBill.find(123) # from a DB shard for client_id = 2
47
52
 
48
- bill1.unique_id
49
- => "62p"
50
- bill2.unique_id
51
- => "62q"
52
- MedicalBill.find_by_unique_id("62p") # from DB shard for client_id = 1
53
- => #<MedicalBill id: 123, client_id: 1>
54
- MedicalBill.find_by_unique_id("62q") # from DB shard for client_id = 2
55
- => #<MedicalBill id: 123, client_id: 2>
53
+ bill1.unique_id
54
+ => 7873
55
+ bill2.unique_id
56
+ => 7874
57
+ MedicalBill.find_by_unique_id(7873) # from DB shard for client_id = 1
58
+ => #<MedicalBill id: 123, client_id: 1>
59
+ MedicalBill.find_by_unique_id(7874) # from DB shard for client_id = 2
60
+ => #<MedicalBill id: 123, client_id: 2>
61
+ ```
56
62
 
57
63
  You can use the internal methods:
58
64
 
59
- MedicalBill.unique_id_from(1, 123) # gives the unique_id from client_id, id
60
- => "62p"
61
- MedicalBill.id_from("62p") # gives the id
62
- => 123
63
- MedicalBill.id_group_from("62q") # gives the client_id
64
- => 2
65
-
66
- And use bits instead of total:
67
-
68
- class MedicalBill < ActiveRecord::Base
69
- unique_by :client_id, bits: 6 # equivalent to total: 64
70
- end
65
+ ```ruby
66
+ MedicalBill.unique_id_from(123, client_id: 1) # gives the unique_id
67
+ => 7873
68
+ MedicalBill.id_from(7873) # gives the id
69
+ => 123
70
+ MedicalBill.id_group_from(7874) # gives the client_id
71
+ => { client_id: 2 }
72
+ ```
71
73
 
72
74
  You can specify multiple unique group attributes:
73
75
 
74
- class MedicalBill < ActiveRecord::Base
75
- unique_by :client_id, :client_part, total: [50, 5]
76
- end
76
+ ```ruby
77
+ class MedicalBill < ActiveRecord::Base
78
+ unique_by client_id: 50, client_part: 5 # total of 50 clients and 5 parts
79
+ end
80
+ ```
77
81
 
78
82
  ### Multiple tables example
79
83
 
80
- You can supply a block to give your own mechanism for determining the
81
- group:
84
+ You can supply a block to give a custom mechanism for determining the group:
82
85
 
83
- class MedicalBill < ActiveRecord::Base
84
- unique_by(total: 2) { 1 }
85
- end
86
- class UtilityBill < ActiveRecord::Base
87
- unique_by(total: 2) { 2 }
88
- end
86
+ ```ruby
87
+ class MedicalBill < ActiveRecord::Base
88
+ unique_by(type: 2) { { type: 1 } }
89
+ end
90
+ class UtilityBill < ActiveRecord::Base
91
+ unique_by(type: 2) { { type: 2 } }
92
+ end
93
+ ```
89
94
 
90
95
  You can supply both group attributes and a block, and the block can also
91
- return an array:
96
+ return more than one field:
92
97
 
93
- class MedicalBill < ActiveRecord::Base
94
- unique(:client_id, :client_part, total: [50, 5, 10, 20]) { [self.x * self.y, self.z / 2] }
95
- end
98
+ ```ruby
99
+ class MedicalBill < ActiveRecord::Base
100
+ unique_by(client_id: 50, client_part: 5, xy: 10, halfz: 20) do
101
+ { xy: self.x * self.y, halfz: self.z / 2 }
102
+ end
103
+ end
104
+ ```
96
105
 
97
106
  ## Not ActiveRecord
98
107
 
99
108
  The generator module is already included in `ActiveRecord::Base`, but if
100
109
  you want the above methods in another class you can extend it:
101
110
 
102
- class MyClass
103
- extend UniqueBy::Generator
104
-
105
- def self.primary_key
106
- :id # or 'id'
107
- end
108
- end
111
+ ```ruby
112
+ class MyClass
113
+ extend UniqueBy::Generator
114
+
115
+ def self.primary_key
116
+ :id # or 'id'
117
+ end
118
+ end
119
+ ```
120
+
121
+ ## See also
122
+
123
+ After adding the unique groups to the id, the unique_id might turn out pretty
124
+ large. You could use [rebase_attr](https://github.com/odedniv/rebase_attr) to
125
+ fix that:
126
+
127
+ ```ruby
128
+ class MedicalBill < ActiveRecord::Base
129
+ unique_by client_id: 500
130
+ rebase_attr :unique_id, to: 32, readable: true
131
+ end
132
+
133
+ bill = MedicalBill.find(3528918) # from a DB shard for client_id = 1
134
+ bill.unique_id
135
+ => "ywr3bxx"
136
+ MedicalBill.find_by_unique_id(MedicalBill.decode_unique_id("ywr3bxx"))
137
+ => #<MedicalBill id: 3528918, client_id: 1>
138
+ ```
109
139
 
110
140
  ## Contributing
111
141
 
@@ -1,3 +1,3 @@
1
1
  module UniqueBy
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/unique_by.rb CHANGED
@@ -11,69 +11,65 @@ module UniqueBy
11
11
  # #unique_id => unique_id
12
12
  # ::find_by_unique_id(unique_id) => find_by_id(id_from(unique_id))
13
13
  # ::find_by_unique_id!(unique_id) => find_by_id!(id_from(unique_id))
14
- def unique_by(*group_block_names, total: nil, bits: nil, &group_block)
15
- bits, total = Array(bits), Array(total)
14
+ def unique_by(**group_totals, &group_block)
15
+ raise ArgumentError, "must pass a group definition (Hash of name => total)" if group_totals.empty?
16
+ raise ArgumentError, "group definition must be a Hash of name => Fixnum, #{group_totals.inspect} given" unless group_totals.values.all? { |t| t.is_a?(Fixnum) }
16
17
 
17
- raise ArgumentError, "must pass either total or bits to #unique_by" \
18
- unless total.any? or bits.any?
19
- raise ArgumentError, "both total (#{total.inspect}) and bits (#{bits.inspect}) passed to #unique_by" \
20
- if total.any? and bits.any?
21
- raise ArgumentError, "must pass a group generator block" \
22
- unless group_block_names.any? or block_given?
23
- raise ArgumentError, "amount of group names (#{group_block_names.length}) doesn't match total/bits (#{total.length + bits.length})" \
24
- if (not block_given? and group_block_names.length != total.length + bits.length) or \
25
- (block_given? and group_block_names.length > total.length + bits.length)
26
-
27
- bits = total.map { |t| Math.log2(t).ceil } if bits.empty?
28
- total = bits.map { |b| 2 ** b }
18
+ bits = Hash[group_totals.map { |k, t| [k, Math.log2(t).ceil] }]
19
+ totals = Hash[bits.map { |k, b| [k, 2 ** b] }] # real total
29
20
 
30
21
  pk = primary_key # converting to a local variable
31
22
 
32
- define_singleton_method :"#{pk}_group_value_from" do |group|
33
- Array(group).each_with_index.reduce(0) do |group_value, (g, i)|
34
- raise TypeError, "group must implement #to_i, #{g.inspect} given" \
35
- unless g.respond_to?(:to_i)
36
- (group_value << bits[i]) + (g.to_i % total[i])
23
+ define_singleton_method :"#{pk}_group_value_from" do |**group|
24
+ raise ArgumentError, "unknown #{pk} group keys: #{group.keys - group_totals.keys}" if (group.keys - group_totals.keys).any?
25
+ raise ArgumentError, "missing #{pk} group keys: #{group_totals.keys - group.keys}" if (group_totals.keys - group.keys).any?
26
+ group_totals.keys.reduce(0) do |group_value, group_name|
27
+ g = group[group_name]
28
+ raise TypeError, "#{pk} group #{group_name} must not be nil" if g.nil?
29
+ raise TypeError, "#{pk} group #{group_name} must implement #to_i, #{g.inspect} given" unless g.respond_to?(:to_i)
30
+ (group_value << bits[group_name]) + (g.to_i % totals[group_name])
37
31
  end
38
32
  end
39
33
 
40
- define_singleton_method :"unique_#{pk}_from" do |id, group|
41
- (id.to_i << bits.inject(&:+)) + send(:"#{pk}_group_value_from", group)
34
+ define_singleton_method :"unique_#{pk}_from" do |id, **group|
35
+ break nil if id.nil?
36
+ raise TypeError, "#{pk} must implement #to_i, #{id.inspect} given" unless id.respond_to?(:to_i)
37
+ (id.to_i << bits.values.inject(&:+)) + send(:"#{pk}_group_value_from", **group)
42
38
  end
43
39
 
44
- define_singleton_method :"#{pk}_from" do |id|
45
- id.to_i >> bits.inject(&:+)
40
+ define_singleton_method :"#{pk}_from" do |unique_id|
41
+ break nil if unique_id.nil?
42
+ raise TypeError, "unique_#{pk} must implement #to_i, #{unique_id.inspect} given" unless unique_id.respond_to?(:to_i)
43
+ unique_id.to_i >> bits.values.inject(&:+)
46
44
  end
47
45
 
48
- define_singleton_method :"#{pk}_group_from" do |id|
49
- group = bits.reverse.zip(total.reverse).map do |b, t|
50
- g = id & (t - 1)
51
- id >>= b
52
- g
53
- end.reverse
54
- group.length == 1 ? group[0] : group
46
+ define_singleton_method :"#{pk}_group_from" do |unique_id|
47
+ break nil if unique_id.nil?
48
+ raise TypeError, "unique_#{pk} must implement #to_i, #{unique_id.inspect} given" unless unique_id.respond_to?(:to_i)
49
+ Hash[group_totals.keys.reverse.map do |group_name|
50
+ g = unique_id & (totals[group_name] - 1)
51
+ unique_id >>= bits[group_name]
52
+ [group_name, g]
53
+ end.reverse]
55
54
  end
56
55
 
57
56
  define_method :"#{pk}_group" do
58
- group = group_block_names.map { |group_block_name| send(group_block_name) }
59
- group.push(*Array(instance_eval(&group_block))) if group_block
60
- raise ArgumentError, "amount of groups (#{group.length}) doesn't match amount of bits/total (#{bits.length})" if group.length != bits.length
61
- group.length == 1 ? group[0] : group
57
+ group_from_block = group_block ? instance_eval(&group_block) : {}
58
+ raise TypeError, "#{pk} group block must return a Hash with any of the following keys: #{group_totals.keys}, #{group_from_block.inspect} given" unless group_from_block.is_a?(Hash)
59
+ raise ArgumentError, "unknown #{pk} group passed to block: #{group_from_block.keys - group_totals.keys}" if (group_from_block.keys - group_totals.keys).any?
60
+ Hash[(group_totals.keys - group_from_block.keys).map { |group_name| [group_name, send(group_name)] }].merge(group_from_block)
62
61
  end
63
62
 
64
63
  define_method :"unique_#{pk}" do
65
- primary_key = send(pk)
66
- raise TypeError, "#{pk} must implement #to_i, #{primary_key.inspect} given" \
67
- unless primary_key.respond_to?(:to_i)
68
- self.class.send(:"unique_#{pk}_from", primary_key.to_i, send(:"#{pk}_group"))
64
+ self.class.send(:"unique_#{pk}_from", send(pk), **send(:"#{pk}_group"))
69
65
  end
70
66
 
71
- define_singleton_method :"find_by_unique_#{pk}" do |id|
72
- send(:"find_by_#{pk}", send(:"#{pk}_from", id))
67
+ define_singleton_method :"find_by_unique_#{pk}" do |unique_id|
68
+ send(:"find_by_#{pk}", send(:"#{pk}_from", unique_id))
73
69
  end
74
70
 
75
- define_singleton_method :"find_by_unique_#{pk}!" do |id|
76
- send(:"find_by_#{pk}!", send(:"#{pk}_from", id))
71
+ define_singleton_method :"find_by_unique_#{pk}!" do |unique_id|
72
+ send(:"find_by_#{pk}!", send(:"#{pk}_from", unique_id))
77
73
  end
78
74
  end
79
75
  end
@@ -44,7 +44,7 @@ describe UniqueBy::Generator do
44
44
  let(:klass) do
45
45
  Class.new(Struct.new(:bill_id, :client_id)) do
46
46
  include BaseBill
47
- unique_by(:client_id, total: 10)
47
+ unique_by(client_id: 10)
48
48
  end
49
49
  end
50
50
 
@@ -57,17 +57,28 @@ describe UniqueBy::Generator do
57
57
  subject { bill1 }
58
58
 
59
59
  describe "class methods" do
60
- specify { expect(klass.bill_id_group_value_from(2)).to eq(2 % 16) }
61
- specify { expect(klass.unique_bill_id_from(431, 2)).to eq(unique_id) }
60
+ specify { expect(klass.bill_id_group_value_from(client_id: 2)).to eq(2 % 16) }
61
+ specify { expect(klass.unique_bill_id_from(431, client_id: 2)).to eq(unique_id) }
62
62
  specify { expect(klass.bill_id_from(unique_id)).to eq(431) }
63
- specify { expect(klass.bill_id_group_from(unique_id)).to eq(2 % 16) }
63
+ specify { expect(klass.bill_id_group_from(unique_id)).to eq(client_id: 2 % 16) }
64
+
65
+ context "nil id" do
66
+ specify { expect(klass.unique_bill_id_from(nil, client_id: 2)).to be_nil }
67
+ specify { expect(klass.bill_id_from(nil)).to be_nil }
68
+ specify { expect(klass.bill_id_group_from(nil)).to be_nil }
69
+ end
64
70
 
65
71
  include_context "finder methods"
66
72
  end
67
73
 
68
74
  describe "instance methods" do
69
- its(:bill_id_group) { should == 2 }
75
+ its(:bill_id_group) { should == { client_id: 2 } }
70
76
  its(:unique_bill_id) { should == unique_id }
77
+
78
+ context "nil id" do
79
+ before { bill1.bill_id = nil }
80
+ its(:unique_bill_id) { should be_nil }
81
+ end
71
82
  end
72
83
  end
73
84
 
@@ -76,16 +87,16 @@ describe UniqueBy::Generator do
76
87
  subject { bill2 }
77
88
 
78
89
  describe "class methods" do
79
- specify { expect(klass.bill_id_group_value_from(7)).to eq(7 % 16) }
80
- specify { expect(klass.unique_bill_id_from(431, 7)).to eq(unique_id) }
90
+ specify { expect(klass.bill_id_group_value_from(client_id: 7)).to eq(7 % 16) }
91
+ specify { expect(klass.unique_bill_id_from(431, client_id: 7)).to eq(unique_id) }
81
92
  specify { expect(klass.bill_id_from(unique_id)).to eq(431) }
82
- specify { expect(klass.bill_id_group_from(unique_id)).to eq(7 % 16) }
93
+ specify { expect(klass.bill_id_group_from(unique_id)).to eq(client_id: 7 % 16) }
83
94
 
84
95
  include_context "finder methods"
85
96
  end
86
97
 
87
98
  describe "instance methods" do
88
- its(:bill_id_group) { should == 7 }
99
+ its(:bill_id_group) { should == { client_id: 7 } }
89
100
  its(:unique_bill_id) { should == unique_id }
90
101
  end
91
102
  end
@@ -94,12 +105,12 @@ describe UniqueBy::Generator do
94
105
  context "tables" do
95
106
  let(:medical_klass) do
96
107
  Class.new(TablesBase) do
97
- unique_by(total: 2) { 10 }
108
+ unique_by(type: 2) { { type: 10 } }
98
109
  end
99
110
  end
100
111
  let(:utility_klass) do
101
112
  Class.new(TablesBase) do
102
- unique_by(total: 2) { 11 }
113
+ unique_by(type: 2) { { type: 11 } }
103
114
  end
104
115
  end
105
116
 
@@ -113,16 +124,16 @@ describe UniqueBy::Generator do
113
124
  subject { medical_bill }
114
125
 
115
126
  describe "class methods" do
116
- specify { expect(medical_klass.bill_id_group_value_from(10)).to eq(10 % 2) }
117
- specify { expect(medical_klass.unique_bill_id_from(839, 10)).to eq(unique_id) }
127
+ specify { expect(medical_klass.bill_id_group_value_from(type: 10)).to eq(10 % 2) }
128
+ specify { expect(medical_klass.unique_bill_id_from(839, type: 10)).to eq(unique_id) }
118
129
  specify { expect(medical_klass.bill_id_from(unique_id)).to eq(839) }
119
- specify { expect(medical_klass.bill_id_group_from(unique_id)).to eq(10 % 2) }
130
+ specify { expect(medical_klass.bill_id_group_from(unique_id)).to eq(type: 10 % 2) }
120
131
 
121
132
  include_context "finder methods"
122
133
  end
123
134
 
124
135
  describe "instance methods" do
125
- its(:bill_id_group) { should == 10 }
136
+ its(:bill_id_group) { should == { type: 10 } }
126
137
  its(:unique_bill_id) { should == unique_id }
127
138
  end
128
139
  end
@@ -133,16 +144,16 @@ describe UniqueBy::Generator do
133
144
  subject { utility_bill }
134
145
 
135
146
  describe "class methods" do
136
- specify { expect(utility_klass.bill_id_group_value_from(11)).to eq(11 % 2) }
137
- specify { expect(utility_klass.unique_bill_id_from(839, 11)).to eq(unique_id) }
147
+ specify { expect(utility_klass.bill_id_group_value_from(type: 11)).to eq(11 % 2) }
148
+ specify { expect(utility_klass.unique_bill_id_from(839, type: 11)).to eq(unique_id) }
138
149
  specify { expect(utility_klass.bill_id_from(unique_id)).to eq(839) }
139
- specify { expect(utility_klass.bill_id_group_from(unique_id)).to eq(11 % 2) }
150
+ specify { expect(utility_klass.bill_id_group_from(unique_id)).to eq(type: 11 % 2) }
140
151
 
141
152
  include_context "finder methods"
142
153
  end
143
154
 
144
155
  describe "instance methods" do
145
- its(:bill_id_group) { should == 11 }
156
+ its(:bill_id_group) { should == { type: 11 } }
146
157
  its(:unique_bill_id) { should == unique_id }
147
158
  end
148
159
  end
@@ -150,9 +161,8 @@ describe UniqueBy::Generator do
150
161
 
151
162
  context "sharded tables" do
152
163
  let(:medical_klass) do
153
-
154
164
  Class.new(ShardedTablesBase) do
155
- unique_by(:client_id, :x, total: [10, 200, 2, 20]) { [10, y] }
165
+ unique_by(client_id: 10, x: 200, type: 2, y: 20) { { type: 10, y: y } }
156
166
  def x
157
167
  53
158
168
  end
@@ -164,7 +174,7 @@ describe UniqueBy::Generator do
164
174
 
165
175
  let(:utility_klass) do
166
176
  Class.new(ShardedTablesBase) do
167
- unique_by(:client_id, :x, bits: [4, 8, 1, 5]) { [11, y] }
177
+ unique_by(client_id: 2**4, x: 2**8, type: 2**1, y: 2**5) { { type: 11, y: y } }
168
178
  def x
169
179
  853
170
180
  end
@@ -178,17 +188,17 @@ describe UniqueBy::Generator do
178
188
  let(:utility_bill) { utility_klass.new(9428, 8, 255) }
179
189
  let(:id) { 9428 }
180
190
 
181
- context "with total" do
191
+ context "medical bill" do
182
192
  let(:klass) { medical_klass }
183
193
  subject { medical_bill }
184
194
 
185
- let(:tempered_group) { [5 % 16, 53 % 256, 10 % 2, 20 % 32] }
195
+ let(:tempered_group) { { client_id: 5 % 16, x: 53 % 256, type: 10 % 2, y: 20 % 32 } }
186
196
  let(:group_value) { ((5 % 16) << 14) + ((53 % 256) << 6) + ((10 % 2) << 5) + (20 % 32) }
187
197
  let(:unique_id) { (9428 << 18) + group_value }
188
198
 
189
199
  describe "class methods" do
190
- specify { expect(medical_klass.bill_id_group_value_from([5, 53, 10, 20])).to eq(group_value) }
191
- specify { expect(medical_klass.unique_bill_id_from(9428, [5, 53, 10, 20])).to eq(unique_id) }
200
+ specify { expect(medical_klass.bill_id_group_value_from(client_id: 5, x: 53, type: 10, y: 20)).to eq(group_value) }
201
+ specify { expect(medical_klass.unique_bill_id_from(9428, client_id: 5, x: 53, type: 10, y: 20)).to eq(unique_id) }
192
202
  specify { expect(medical_klass.bill_id_from(unique_id)).to eq(9428) }
193
203
  specify { expect(medical_klass.bill_id_group_from(unique_id)).to eq(tempered_group) }
194
204
 
@@ -196,22 +206,22 @@ describe UniqueBy::Generator do
196
206
  end
197
207
 
198
208
  describe "instance methods" do
199
- its(:bill_id_group) { should == [5, 53, 10, 20] }
209
+ its(:bill_id_group) { should == { client_id: 5, x: 53, type: 10, y: 20 } }
200
210
  its(:unique_bill_id) { should == unique_id }
201
211
  end
202
212
  end
203
213
 
204
- context "with bits" do
214
+ context "utility bill" do
205
215
  let(:klass) { utility_klass }
206
216
  subject { utility_bill }
207
217
 
208
- let(:tempered_group) { [8 % 16, 853 % 256, 11 % 2, 40 % 32] }
218
+ let(:tempered_group) { { client_id: 8 % 16, x: 853 % 256, type: 11 % 2, y: 40 % 32 } }
209
219
  let(:group_value) { ((8 % 16) << 14) + ((853 % 256) << 6) + ((11 % 2) << 5) + (40 % 32) }
210
220
  let(:unique_id) { (9428 << 18) + group_value }
211
221
 
212
222
  describe "class methods" do
213
- specify { expect(utility_klass.bill_id_group_value_from([8, 853, 11, 40])).to eq(group_value) }
214
- specify { expect(utility_klass.unique_bill_id_from(9428, [8, 853, 11, 40])).to eq(unique_id) }
223
+ specify { expect(utility_klass.bill_id_group_value_from(client_id: 8, x: 853, type: 11, y: 40)).to eq(group_value) }
224
+ specify { expect(utility_klass.unique_bill_id_from(9428, client_id: 8, x: 853, type: 11, y: 40)).to eq(unique_id) }
215
225
  specify { expect(utility_klass.bill_id_from(unique_id)).to eq(9428) }
216
226
  specify { expect(utility_klass.bill_id_group_from(unique_id)).to eq(tempered_group) }
217
227
 
@@ -219,35 +229,93 @@ describe UniqueBy::Generator do
219
229
  end
220
230
 
221
231
  describe "instance methods" do
222
- its(:bill_id_group) { should == [8, 853, 11, 40] }
232
+ its(:bill_id_group) { should == { client_id: 8, x: 853, type: 11, y: 40 } }
223
233
  its(:unique_bill_id) { should == unique_id }
224
234
  end
225
235
  end
226
236
  end
227
237
 
228
238
  context "errors" do
229
- let(:klass) do
230
- Class.new do
231
- include BaseBill
239
+ describe "#unique_by" do
240
+ let(:klass) do
241
+ Class.new do
242
+ include BaseBill
243
+ end
232
244
  end
233
- end
234
245
 
235
- describe "#unique_by" do
236
- specify { expect { klass.unique_by(:x) }.to raise_error(ArgumentError, "must pass either total or bits to #unique_by") }
237
- specify { expect { klass.unique_by(:x, total: [5, 4], bits: [2, 7]) }.to raise_error(ArgumentError, "both total ([5, 4]) and bits ([2, 7]) passed to #unique_by") }
238
- specify { expect { klass.unique_by(total: 5) }.to raise_error(ArgumentError, "must pass a group generator block") }
239
- specify { expect { klass.unique_by(:x, :y, total: 5) }.to raise_error(ArgumentError, "amount of group names (2) doesn't match total/bits (1)") }
240
- specify { expect { klass.unique_by(:x, :y, total: [3, 2, 5]) }.to raise_error(ArgumentError, "amount of group names (2) doesn't match total/bits (3)") }
241
- specify { expect { klass.unique_by(:x, :y, total: [3, 2, 5]) { } }.not_to raise_error }
242
- specify { expect { klass.unique_by(:x, :y, :z, bits: [3, 2]) { } }.to raise_error(ArgumentError, "amount of group names (3) doesn't match total/bits (2)") }
246
+ specify { expect { klass.unique_by }.to raise_error(ArgumentError, "must pass a group definition (Hash of name => total)") }
247
+ specify { expect { klass.unique_by(x: :a) }.to raise_error(ArgumentError, "group definition must be a Hash of name => Fixnum, {:x=>:a} given") }
243
248
  end
244
249
 
245
250
  describe "class methods" do
246
- pending
251
+ let(:klass) do
252
+ Class.new(Struct.new(:bill_id, :client_id)) do
253
+ include BaseBill
254
+ unique_by client_id: 10
255
+ end
256
+ end
257
+
258
+ specify { expect { klass.bill_id_group_value_from(x: 2) }.to raise_error(ArgumentError, "unknown bill_id group keys: [:x]") }
259
+ specify { expect { klass.bill_id_group_value_from(client_id: 5, x: 2) }.to raise_error(ArgumentError, "unknown bill_id group keys: [:x]") }
260
+ specify { expect { klass.bill_id_group_value_from() }.to raise_error(ArgumentError, "missing bill_id group keys: [:client_id]") }
261
+ specify { expect { klass.bill_id_group_value_from(client_id: nil) }.to raise_error(TypeError, "bill_id group client_id must not be nil") }
262
+ specify { expect { klass.bill_id_group_value_from(client_id: :a) }.to raise_error(TypeError, "bill_id group client_id must implement #to_i, :a given") }
263
+ specify { expect { klass.unique_bill_id_from(431, client_id: nil) }.to raise_error(TypeError, "bill_id group client_id must not be nil") }
264
+ specify { expect { klass.unique_bill_id_from(431, client_id: :a) }.to raise_error(TypeError, "bill_id group client_id must implement #to_i, :a given") }
265
+ specify { expect { klass.unique_bill_id_from(:a, client_id: 5) }.to raise_error(TypeError, "bill_id must implement #to_i, :a given") }
266
+ specify { expect { klass.bill_id_from(:a) }.to raise_error(TypeError, "unique_bill_id must implement #to_i, :a given") }
267
+ specify { expect { klass.bill_id_group_from(:a) }.to raise_error(TypeError, "unique_bill_id must implement #to_i, :a given") }
247
268
  end
248
269
 
249
270
  describe "instance methods" do
250
- pending
271
+ let(:klass) do
272
+ Class.new(Struct.new(:bill_id, :client_id, :block)) do
273
+ include BaseBill
274
+ unique_by(client_id: 10, x: 5) { block }
275
+ end
276
+ end
277
+
278
+ context "nil group" do
279
+ let(:bill) { klass.new(431, nil, { x: 2 }) }
280
+ specify { expect { bill.unique_bill_id }.to raise_error(TypeError, "bill_id group client_id must not be nil") }
281
+ end
282
+
283
+ context "invalid group" do
284
+ let(:bill) { klass.new(431, :a, { x: 2 }) }
285
+ specify { expect { bill.unique_bill_id }.to raise_error(TypeError, "bill_id group client_id must implement #to_i, :a given") }
286
+ end
287
+
288
+ context "nil block group" do
289
+ let(:bill) { klass.new(431, 5, { x: nil }) }
290
+ specify { expect { bill.unique_bill_id }.to raise_error(TypeError, "bill_id group x must not be nil") }
291
+ end
292
+
293
+ context "invalid block group" do
294
+ let(:bill) { klass.new(431, 5, :a) }
295
+ specify { expect { bill.unique_bill_id }.to raise_error(TypeError, "bill_id group block must return a Hash with any of the following keys: [:client_id, :x], :a given") }
296
+ end
297
+
298
+ context "invalid block group value" do
299
+ let(:bill) { klass.new(431, 5, { x: :a }) }
300
+ specify { expect { bill.unique_bill_id }.to raise_error(TypeError, "bill_id group x must implement #to_i, :a given") }
301
+ end
302
+
303
+ context "unknown block group keys" do
304
+ context "too few" do
305
+ let(:bill) { klass.new(431, 5, { }) }
306
+ specify { expect { bill.unique_bill_id }.to raise_error(NameError, "undefined method `x' for #<struct bill_id=431, client_id=5, block={}>") }
307
+ end
308
+
309
+ context "too many" do
310
+ let(:bill) { klass.new(431, 5, { x: 2, y: 12 }) }
311
+ specify { expect { bill.unique_bill_id }.to raise_error(ArgumentError, "unknown bill_id group passed to block: [:y]") }
312
+ end
313
+
314
+ context "different" do
315
+ let(:bill) { klass.new(431, 5, { y: 12 }) }
316
+ specify { expect { bill.unique_bill_id }.to raise_error(ArgumentError, "unknown bill_id group passed to block: [:y]") }
317
+ end
318
+ end
251
319
  end
252
320
  end
253
321
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unique_by
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oded Niv
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-17 00:00:00.000000000 Z
11
+ date: 2014-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler