unique_by 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4360207db16c47e063005d3f50983411710de6f8
4
- data.tar.gz: f2aa0cb2ff36558ca288fe4f8397645edd55a28a
3
+ metadata.gz: c877eeb4bee389ff8f304f38825a35ab1ce2c398
4
+ data.tar.gz: 87108e22b935547a8deb63355979f9fd2a16eef1
5
5
  SHA512:
6
- metadata.gz: 2e91b8a6f86e7b1fb936807adf396e63ccecba95f3b8ab9ae892e17f6671ecfaba7928b960a6e581a9a8455f4a3a0e48635033fa5d2dfcbad783fdf94d001daf
7
- data.tar.gz: ad167e6e1778ed2d855144b1c548a330e2b293664d5234746119dc5026a84497448c9f937a475d8e42616c6fcf3d774563482bd8c124de433f4f9bb78f1f4829
6
+ metadata.gz: e5e50dd6ce9bcb170af31c2e171388c2bec13e650685f62cded4a11fefeaf6d9d8d81a3915f37c4254ba002ce80b1fe0f15938da0356fba6b1020ffac2bc4419
7
+ data.tar.gz: 328cdd45129518b3e131ecd8e562ae6e216de0f4ad9da433e2b9e3a9a39c6b597c16fd10d1e3c0d368b43c4ca2ade12c68d5e1b9dc4a34377b169de0b50ef6c4
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -1,3 +1,3 @@
1
1
  module UniqueBy
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/unique_by.rb CHANGED
@@ -3,7 +3,8 @@ require "unique_by/version"
3
3
  module UniqueBy
4
4
  module Generator
5
5
  # For a primary_key 'id', generates:
6
- # ::unique_id_from(group, id) => unique_id
6
+ # ::id_group_value_from(group) => group_value
7
+ # ::unique_id_from(id, group) => unique_id
7
8
  # ::id_from(unique_id) => id
8
9
  # ::id_group_from(unique_id) => group
9
10
  # #id_group => group
@@ -13,56 +14,66 @@ module UniqueBy
13
14
  def unique_by(*group_block_names, total: nil, bits: nil, &group_block)
14
15
  bits, total = Array(bits), Array(total)
15
16
 
16
- raise ArgumentError, "must pass either bits or total to #unique_by" \
17
- unless bits.any? or total.any?
18
- raise ArgumentError, "both bits (#{bits.inspect}) and total (#{total.inspect}) passed to #unique_by" \
19
- if bits.any? and total.any?
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?
20
21
  raise ArgumentError, "must pass a group generator block" \
21
22
  unless group_block_names.any? or block_given?
22
- raise ArgumentError, "amount of group names (#{group_block_names.length}) doesn't match amount of bits/total (#{bits.length + total.length})" \
23
- if group_block_names.any? and not block_given? and group_block_names.length != bits.length + total.length
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)
24
26
 
25
27
  bits = total.map { |t| Math.log2(t).ceil } if bits.empty?
26
28
  total = bits.map { |b| 2 ** b }
27
29
 
28
30
  pk = primary_key # converting to a local variable
29
31
 
30
- define_singleton_method :"unique_#{pk}_from" do |group_value, value|
31
- (value.to_i << bits.sum) + group_value.to_i
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])
37
+ end
38
+ end
39
+
40
+ define_singleton_method :"unique_#{pk}_from" do |id, group|
41
+ (id.to_i << bits.inject(&:+)) + send(:"#{pk}_group_value_from", group)
32
42
  end
33
43
 
34
- define_singleton_method :"#{pk}_from" do |value|
35
- value.to_i >> bits
44
+ define_singleton_method :"#{pk}_from" do |id|
45
+ id.to_i >> bits.inject(&:+)
36
46
  end
37
47
 
38
- define_singleton_method :"#{pk}_group_from" do |value|
39
- value.to_i & ((2 ** bits.sum) - 1)
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
40
55
  end
41
56
 
42
57
  define_method :"#{pk}_group" do
43
- group_values = group_block_names.map { |group_block_name| send(group_block_name) }
44
- group_values.push(*Array(instance_eval(group_block))) if group_block
45
- raise "amount of groups (#{group_values.length}) doesn't match amount of bits/total (#{bits.length})" if group_values.length != bits.length
46
- group_values.each_with_index.reduce(0) do |group, (group_value, i)|
47
- raise "group must implement #to_i, #{group_value} given" \
48
- unless group_value.respond_to?(&:to_i)
49
- (group << bits[i]) + (group_value.to_i % total[i])
50
- end
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
51
62
  end
52
63
 
53
64
  define_method :"unique_#{pk}" do
54
65
  primary_key = send(pk)
55
- raise "#{pk} must implement #to_i, #{primary_key.inspect} given" \
66
+ raise TypeError, "#{pk} must implement #to_i, #{primary_key.inspect} given" \
56
67
  unless primary_key.respond_to?(:to_i)
57
- self.class.send(:"unique_#{pk}_from", send(:"#{pk}_group"), primary_key.to_i)
68
+ self.class.send(:"unique_#{pk}_from", primary_key.to_i, send(:"#{pk}_group"))
58
69
  end
59
70
 
60
- define_singleton_method :"find_by_unique_#{pk}" do |value|
61
- send(:"find_by_#{pk}". send(:"#{pk}_from", value))
71
+ define_singleton_method :"find_by_unique_#{pk}" do |id|
72
+ send(:"find_by_#{pk}", send(:"#{pk}_from", id))
62
73
  end
63
74
 
64
- define_singleton_method :"find_by_unique_#{pk}!" do |value|
65
- send(:"find_by_#{pk}!". send(:"#{pk}_from", value))
75
+ define_singleton_method :"find_by_unique_#{pk}!" do |id|
76
+ send(:"find_by_#{pk}!", send(:"#{pk}_from", id))
66
77
  end
67
78
  end
68
79
  end
@@ -0,0 +1,91 @@
1
+ require 'rspec/its'
2
+
3
+ # This file was generated by the `rspec --init` command. Conventionally, all
4
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
6
+ # file to always be loaded, without a need to explicitly require it in any files.
7
+ #
8
+ # Given that it is always loaded, you are encouraged to keep this file as
9
+ # light-weight as possible. Requiring heavyweight dependencies from this file
10
+ # will add to the boot time of your test suite on EVERY test run, even for an
11
+ # individual file that may not need all of that loaded. Instead, consider making
12
+ # a separate helper file that requires the additional dependencies and performs
13
+ # the additional setup, and require it from the spec files that actually need it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
23
+ config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
36
+ config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
40
+ mocks.verify_partial_doubles = true
41
+ end
42
+
43
+ # The settings below are suggested to provide a good initial experience
44
+ # with RSpec, but feel free to customize to your heart's content.
45
+ =begin
46
+ # These two settings work together to allow you to limit a spec run
47
+ # to individual examples or groups you care about by tagging them with
48
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
49
+ # get run.
50
+ config.filter_run :focus
51
+ config.run_all_when_everything_filtered = true
52
+
53
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
54
+ # For more details, see:
55
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
56
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
57
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
58
+ config.disable_monkey_patching!
59
+
60
+ # This setting enables warnings. It's recommended, but in some cases may
61
+ # be too noisy due to issues in dependencies.
62
+ config.warnings = true
63
+
64
+ # Many RSpec users commonly either run the entire suite or an individual
65
+ # file, and it's useful to allow more verbose output when running an
66
+ # individual spec file.
67
+ if config.files_to_run.one?
68
+ # Use the documentation formatter for detailed output,
69
+ # unless a formatter has already been configured
70
+ # (e.g. via a command-line flag).
71
+ config.default_formatter = 'doc'
72
+ end
73
+
74
+ # Print the 10 slowest examples and example groups at the
75
+ # end of the spec run, to help surface which specs are running
76
+ # particularly slow.
77
+ config.profile_examples = 10
78
+
79
+ # Run specs in random order to surface order dependencies. If you find an
80
+ # order dependency and want to debug it, you can fix the order by providing
81
+ # the seed, which is printed after each run.
82
+ # --seed 1234
83
+ config.order = :random
84
+
85
+ # Seed global randomization in this process using the `--seed` CLI option.
86
+ # Setting this allows you to use `--seed` to deterministically reproduce
87
+ # test failures related to randomization by passing the same `--seed` value
88
+ # as the one that triggered the failure.
89
+ Kernel.srand config.seed
90
+ =end
91
+ end
@@ -0,0 +1,253 @@
1
+ require 'unique_by'
2
+
3
+ module BaseBill
4
+ def self.included(base)
5
+ super
6
+ base.extend UniqueBy::Generator
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def primary_key
12
+ :bill_id
13
+ end
14
+
15
+ def find_by_bill_id(bill_id)
16
+ end
17
+
18
+ def find_by_bill_id!(bill_id)
19
+ end
20
+ end
21
+ end
22
+
23
+ class TablesBase < Struct.new(:bill_id)
24
+ include BaseBill
25
+ end
26
+
27
+ class ShardedTablesBase < Struct.new(:bill_id, :client_id, :x)
28
+ include BaseBill
29
+ end
30
+
31
+ describe UniqueBy::Generator do
32
+ shared_context "finder methods" do
33
+ specify "find_by_bill_id" do
34
+ expect(klass).to receive(:find_by_bill_id).with(id)
35
+ klass.find_by_unique_bill_id(unique_id)
36
+ end
37
+ specify "find_by_bill_id!" do
38
+ expect(klass).to receive(:find_by_bill_id!).with(id)
39
+ klass.find_by_unique_bill_id!(unique_id)
40
+ end
41
+ end
42
+
43
+ context "shards" do
44
+ let(:klass) do
45
+ Class.new(Struct.new(:bill_id, :client_id)) do
46
+ include BaseBill
47
+ unique_by(:client_id, total: 10)
48
+ end
49
+ end
50
+
51
+ let(:bill1) { klass.new(431, 2) }
52
+ let(:bill2) { klass.new(431, 7) }
53
+ let(:id) { 431 }
54
+
55
+ context "bill1" do
56
+ let(:unique_id) { (431 << 4) + (2 % 16) }
57
+ subject { bill1 }
58
+
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) }
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) }
64
+
65
+ include_context "finder methods"
66
+ end
67
+
68
+ describe "instance methods" do
69
+ its(:bill_id_group) { should == 2 }
70
+ its(:unique_bill_id) { should == unique_id }
71
+ end
72
+ end
73
+
74
+ context "bill2" do
75
+ let(:unique_id) { (431 << 4) + (7 % 16) }
76
+ subject { bill2 }
77
+
78
+ 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) }
81
+ 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) }
83
+
84
+ include_context "finder methods"
85
+ end
86
+
87
+ describe "instance methods" do
88
+ its(:bill_id_group) { should == 7 }
89
+ its(:unique_bill_id) { should == unique_id }
90
+ end
91
+ end
92
+ end
93
+
94
+ context "tables" do
95
+ let(:medical_klass) do
96
+ Class.new(TablesBase) do
97
+ unique_by(total: 2) { 10 }
98
+ end
99
+ end
100
+ let(:utility_klass) do
101
+ Class.new(TablesBase) do
102
+ unique_by(total: 2) { 11 }
103
+ end
104
+ end
105
+
106
+ let(:medical_bill) { medical_klass.new(839) }
107
+ let(:utility_bill) { utility_klass.new(839) }
108
+ let(:id) { 839 }
109
+
110
+ context "medical bill" do
111
+ let(:klass) { medical_klass }
112
+ let(:unique_id) { (839 << 1) + (10 % 2) }
113
+ subject { medical_bill }
114
+
115
+ 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) }
118
+ 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) }
120
+
121
+ include_context "finder methods"
122
+ end
123
+
124
+ describe "instance methods" do
125
+ its(:bill_id_group) { should == 10 }
126
+ its(:unique_bill_id) { should == unique_id }
127
+ end
128
+ end
129
+
130
+ context "utility bill" do
131
+ let(:klass) { utility_klass }
132
+ let(:unique_id) { (839 << 1) + (11 % 2) }
133
+ subject { utility_bill }
134
+
135
+ 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) }
138
+ 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) }
140
+
141
+ include_context "finder methods"
142
+ end
143
+
144
+ describe "instance methods" do
145
+ its(:bill_id_group) { should == 11 }
146
+ its(:unique_bill_id) { should == unique_id }
147
+ end
148
+ end
149
+ end
150
+
151
+ context "sharded tables" do
152
+ let(:medical_klass) do
153
+
154
+ Class.new(ShardedTablesBase) do
155
+ unique_by(:client_id, :x, total: [10, 200, 2, 20]) { [10, y] }
156
+ def x
157
+ 53
158
+ end
159
+ def y
160
+ 20
161
+ end
162
+ end
163
+ end
164
+
165
+ let(:utility_klass) do
166
+ Class.new(ShardedTablesBase) do
167
+ unique_by(:client_id, :x, bits: [4, 8, 1, 5]) { [11, y] }
168
+ def x
169
+ 853
170
+ end
171
+ def y
172
+ 40
173
+ end
174
+ end
175
+ end
176
+
177
+ let(:medical_bill) { medical_klass.new(9428, 5, 128) }
178
+ let(:utility_bill) { utility_klass.new(9428, 8, 255) }
179
+ let(:id) { 9428 }
180
+
181
+ context "with total" do
182
+ let(:klass) { medical_klass }
183
+ subject { medical_bill }
184
+
185
+ let(:tempered_group) { [5 % 16, 53 % 256, 10 % 2, 20 % 32] }
186
+ let(:group_value) { ((5 % 16) << 14) + ((53 % 256) << 6) + ((10 % 2) << 5) + (20 % 32) }
187
+ let(:unique_id) { (9428 << 18) + group_value }
188
+
189
+ 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) }
192
+ specify { expect(medical_klass.bill_id_from(unique_id)).to eq(9428) }
193
+ specify { expect(medical_klass.bill_id_group_from(unique_id)).to eq(tempered_group) }
194
+
195
+ include_context "finder methods"
196
+ end
197
+
198
+ describe "instance methods" do
199
+ its(:bill_id_group) { should == [5, 53, 10, 20] }
200
+ its(:unique_bill_id) { should == unique_id }
201
+ end
202
+ end
203
+
204
+ context "with bits" do
205
+ let(:klass) { utility_klass }
206
+ subject { utility_bill }
207
+
208
+ let(:tempered_group) { [8 % 16, 853 % 256, 11 % 2, 40 % 32] }
209
+ let(:group_value) { ((8 % 16) << 14) + ((853 % 256) << 6) + ((11 % 2) << 5) + (40 % 32) }
210
+ let(:unique_id) { (9428 << 18) + group_value }
211
+
212
+ 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) }
215
+ specify { expect(utility_klass.bill_id_from(unique_id)).to eq(9428) }
216
+ specify { expect(utility_klass.bill_id_group_from(unique_id)).to eq(tempered_group) }
217
+
218
+ include_context "finder methods"
219
+ end
220
+
221
+ describe "instance methods" do
222
+ its(:bill_id_group) { should == [8, 853, 11, 40] }
223
+ its(:unique_bill_id) { should == unique_id }
224
+ end
225
+ end
226
+ end
227
+
228
+ context "errors" do
229
+ let(:klass) do
230
+ Class.new do
231
+ include BaseBill
232
+ end
233
+ end
234
+
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)") }
243
+ end
244
+
245
+ describe "class methods" do
246
+ pending
247
+ end
248
+
249
+ describe "instance methods" do
250
+ pending
251
+ end
252
+ end
253
+ end
data/unique_by.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["oded.niv@gmail.com"]
11
11
  spec.summary = %q{Specify uniqueness group for an attribute.}
12
12
  spec.description = %q{Allows uniqueness of a record when sharding (specifying the shard ID as the group) or span accross tables (receipts).}
13
- spec.homepage = ""
13
+ spec.homepage = "https://github.com/odedniv/unique_by"
14
14
  spec.license = "UNLICENSE"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
@@ -20,4 +20,5 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.6"
22
22
  spec.add_development_dependency "rspec", "~> 3.1"
23
+ spec.add_development_dependency "rspec-its", "~> 1.0"
23
24
  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: 0.0.1
4
+ version: 1.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-16 00:00:00.000000000 Z
11
+ date: 2014-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-its
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
41
55
  description: Allows uniqueness of a record when sharding (specifying the shard ID
42
56
  as the group) or span accross tables (receipts).
43
57
  email:
@@ -47,14 +61,17 @@ extensions: []
47
61
  extra_rdoc_files: []
48
62
  files:
49
63
  - .gitignore
64
+ - .rspec
50
65
  - Gemfile
51
66
  - README.md
52
67
  - Rakefile
53
68
  - UNLICENSE
54
69
  - lib/unique_by.rb
55
70
  - lib/unique_by/version.rb
71
+ - spec/spec_helper.rb
72
+ - spec/unique_by_spec.rb
56
73
  - unique_by.gemspec
57
- homepage: ''
74
+ homepage: https://github.com/odedniv/unique_by
58
75
  licenses:
59
76
  - UNLICENSE
60
77
  metadata: {}
@@ -78,4 +95,6 @@ rubygems_version: 2.2.2
78
95
  signing_key:
79
96
  specification_version: 4
80
97
  summary: Specify uniqueness group for an attribute.
81
- test_files: []
98
+ test_files:
99
+ - spec/spec_helper.rb
100
+ - spec/unique_by_spec.rb